diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 00000000000..459af934eab --- /dev/null +++ b/.asf.yaml @@ -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. +# + +# `.asf.yaml` is a branch-specific YAML configuration file for Git repositories to control features such as notifications, GitHub settings, etc. +# See its documentation for details: https://github.com/apache/infrastructure-asfyaml + +# Bare minimum `notifications` to +# +# 1. Forward GitHub _activity_ to `notifications@` +# 2. Forward commits to `commits@` +# 3. Forward `dependabot` PRs to `robots@` +# +# Note that `notifications` are merged with the defaults accessible from: https://gitbox.apache.org/schemes.cgi?logging-log4j2 +notifications: + commits: commits@logging.apache.org + issues: notifications@logging.apache.org + pullrequests: notifications@logging.apache.org + pullrequests_bot_dependabot: robots@logging.apache.org + jira_options: link label worklog + discussions: dev@logging.apache.org + +github: + description: "Apache Log4j is a versatile, feature-rich, efficient logging API and backend for Java." + homepage: https://logging.apache.org/log4j/2.x + features: + issues: true + discussions: true + projects: true + autolink_jira: + - LOG4J2 + labels: + - apache + - api + - java + - jvm + - library + - log4j + - log4j2 + - logging + - logger + - api + - syslog + + # Pull Request settings: + # https://github.com/apache/infrastructure-asfyaml#pull-request-settings + pull_requests: + # allow auto-merge + allow_auto_merge: true + # enable updating head branches of pull requests + allow_update_branch: true + # auto-delete head branches after being merged + del_branch_on_merge: true + + # Enforce squashing while merging PRs. + # Otherwise, the git log gets polluted severely. + enabled_merge_buttons: + squash: true + merge: false + rebase: false + + # Enforce Review-then-Commit + protected_branches: + 2.x: + # All commits must be signed + required_signatures: true + # All reviews must be addressed before merging + required_conversation_resolution: true + # Require checks to pass before merging + required_status_checks: + checks: + # The GitHub Actions app: 15368 + - app_id: 15368 + context: "build / build (ubuntu-latest)" + # The GitHub Advanced Security app: 57789 + - app_id: 57789 + context: "CodeQL" + # At least one positive review must be present + required_pull_request_reviews: + required_approving_review_count: 1 + main: + # All commits must be signed + required_signatures: true + # All reviews must be addressed before merging + required_conversation_resolution: true + # Require checks to pass before merging + required_status_checks: + checks: + # The GitHub Actions app: 15368 + - app_id: 15368 + context: "build / build (ubuntu-latest)" + # The GitHub Advanced Security app: 57789 + - app_id: 57789 + context: "CodeQL" + # At least one positive review must be present + required_pull_request_reviews: + required_approving_review_count: 1 diff --git a/.cherry_picker.toml b/.cherry_picker.toml new file mode 100644 index 00000000000..7b2b33db06a --- /dev/null +++ b/.cherry_picker.toml @@ -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. + +team = "apache" +repo = "logging-log4j2" +check_sha= "3da98f7c9de9bf4abb17e10dad678e05ab658a3a" +fix_commit_msg = false +default_branch = "2.x" +require_version_in_branch_name=false +draft_pr = true 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..bc0e569cbc5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,21 @@ -mvnw.cmd eol=crlf +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# All text files with LF line endings +* text=auto eol=lf +# Maven Wrapper cmd script +/mvnw.cmd eol=crlf +# Maven Wrapper need LF line endings +/.mvn/wrapper/maven-wrapper.properties eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..a4c3c7f0a16 --- /dev/null +++ b/.github/FUNDING.yml @@ -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. +# +## +# This file controls the "Sponsor" button in this repo. +# For details see: +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository +# +# WARNING: the `github` key accepts only 4 GitHub user ids, so we can not use this feature. +# +custom: "https://logging.apache.org/support.html#sponsors" +tidelift: "maven/org.apache.logging.log4j:log4j-core" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..bc1d782d3c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Submit a bug report +--- + +## Description + +[A clear and concise description of what the bug is.] + +## Configuration + +**Version:** [Log4j version] + +**Operating system:** [OS and version] + +**JDK:** [JDK distribution and version] + +## Logs + +``` +[Stacktraces, errors, etc. relevant applications logs.] +``` + +## Reproduction + +[An isolated test reproducing the test. +JUnit tests similar to the ones in the code base are extremely appreciated.] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..57770a1e59e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,9 @@ +--- +name: Feature request +about: Submit a feature request +--- + +**Warning!** +It is highly recommended to discuss feature requests in [the mailing lists](https://logging.apache.org/log4j/2.x/support.html) first. + +[A clear and concise description of the feature requested.] diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000000..5bc8e958166 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,8 @@ +--- +name: Question +about: Ask a question +--- + +As clearly indicated in [the Log4j support page](https://logging.apache.org/log4j/2.x/support.html#issues), **please use either GitHub Discussions or mailing lists for questions!** + +Issues asking questions will be removed, and asked to post questions to GitHub Discussions or mailing lists instead. diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000000..5e7fa1e63ce --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,249 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████ ██ +# ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██ ██ +# ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ +# ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +# ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████ ██ +# +# `dependabot.yaml` must be stored in the `.github` directory of the default branch[1]. +# +# 1. Make all your changes to this file! +# Don't create another `dependabot.yaml` – it will simply be discarded. +# +# 2. Always associate your entries to a branch! +# For instance, use `target-branch` in `updates` entries +# +# [1] https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# + +version: 2 + +# Fix the Maven Central to the ASF repository to work around: https://github.com/dependabot/dependabot-core/issues/8329 +registries: + maven-central: + type: maven-repository + url: https://repo.maven.apache.org/maven2 + +updates: + + - package-ecosystem: maven + directories: + - "/log4j-1.2-api" + - "/log4j-api-test" + - "/log4j-api" + - "/log4j-appserver" + - "/log4j-cassandra" + - "/log4j-core-fuzz-test" + - "/log4j-core-its" + - "/log4j-core-test" + - "/log4j-core" + - "/log4j-couchdb" + - "/log4j-docker" + - "/log4j-fuzz-test" + - "/log4j-iostreams" + - "/log4j-jakarta-jms" + - "/log4j-jakarta-smtp" + - "/log4j-jakarta-web" + - "/log4j-jcl" + - "/log4j-jdbc-dbcp2" + - "/log4j-jpa" + - "/log4j-jpl" + - "/log4j-jul" + - "/log4j-layout-template-json-fuzz-test" + - "/log4j-layout-template-json-test" + - "/log4j-layout-template-json" + - "/log4j-mongodb" + # `log4j-mongodb4` is in a separate run + - "/log4j-osgi-test" + - "/log4j-parent" + - "/log4j-perf-test" + # `log4j-slf4j-impl` is in a separate run + - "/log4j-slf4j2-impl-fuzz-test" + - "/log4j-slf4j2-impl" + - "/log4j-spring-boot" + - "/log4j-spring-cloud-config-client" + - "/log4j-taglib" + - "/log4j-to-jul" + - "/log4j-to-slf4j" + - "/log4j-web" + open-pull-requests-limit: 10 + schedule: + interval: "daily" + target-branch: "2.x" + registries: + - maven-central + ignore: + # `com.github.spotbugs:spotbugs-annotations:4.9.0` and onwards require Java 11 + - dependency-name: "com.github.spotbugs:spotbugs-annotations" + versions: [ "[4.9.0,)" ] + # Jetty 10.x does not have an internal logging API + - dependency-name: "org.eclipse.jetty:*" + versions: [ "[10,)" ] + # EclipseLink 3.x is Jakarta EE 9 + - dependency-name: "org.eclipse.persistence:*" + versions: [ "[3,)" ] + # Spring 6.x is Jakarta EE 9 + - dependency-name: "org.springframework:*" + versions: [ "[6,)" ] + # Spring Boot 3.x is Jakarta EE 9 + - dependency-name: "org.springframework.boot:*" + versions: [ "[3,)" ] + # Spring Cloud 2022.x is Jakarta EE 9 + - dependency-name: "org.springframework.cloud:*" + versions: [ "[2021,)" ] + # Tomcat Juli 10.1.x requires Java 11 + - dependency-name: "org.apache.tomcat:*" + versions: [ "[10.1,)" ] + # Keep Logback version 1.2.x + - dependency-name: "ch.qos.logback:*" + versions: [ "[1.3,)" ] + # Mockito 5.x requires Java 11 + - dependency-name: "org.mockito:*" + versions: [ "[5,)" ] + # JUnit Pioneer 2.x requires Java 11 + - dependency-name: "org.junit-pioneer:*" + versions: [ "[2,)" ] + # Apache Cassandra: keep version 3.x + - dependency-name: "org.apache.cassandra:*" + versions: [ "[4,)" ] + # Kubernetes: keep version 5.x + - dependency-name: "io.fabric8:*" + versions: [ "[6,)" ] + # `com.conversantmedia:disruptor` 1.2.16 requires Java 9 + - dependency-name: "com.conversantmedia:disruptor" + versions: [ "[1.2.16,)" ] + # Keep Jakarta EE at version 9.0 + - dependency-name: "jakarta.platform:*" + versions: [ "[10,)" ] + # OpenRewrite is quite noisy. Let us skip patch and minor updates: + - dependency-name: "org.openrewrite:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + - dependency-name: "org.openrewrite.maven:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + - dependency-name: "org.openrewrite.recipe:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + # Json Unit 3.x requires Java 17 + - dependency-name: "net.javacrumbs.json-unit:*" + versions: [ "[3,)" ] + # Update both `disruptor.version` to latest 3.x version + # and `disruptor4.version` to latest 4.x version + - dependency-name: "com.lmax:disruptor" + update-types: [ "version-update:semver-major" ] + # WebCompere System Stubs requires Java 11 + - dependency-name: "uk.org.webcompere:*" + versions: [ "[2.1,)" ] + # Plexus Utils 4.x are for Maven 4.x + - dependency-name: "org.codehaus.plexus:plexus-utils" + versions: [ "[4,)" ] + # H2 version 2.3.x requires Java 11 + - dependency-name: "com.h2database:h2" + versions: [ "[2.3,)" ] + # The Console Appender only support JANSI 1.x for now + # see https://github.com/apache/logging-log4j2/issues/1736 + - dependency-name: "org.fusesource.jansi:jansi" + update-types: [ "version-update:semver-major" ] + # SLF4J should not perform major version upgrades + - dependency-name: "org.slf4j:slf4j-api" + update-types: [ "version-update:semver-major" ] + # Kafka 4.x is not compatible with our appender + - dependency-name: "org.apache.kafka:*" + versions: [ "[4,)" ] + + - package-ecosystem: maven + directories: + - "/log4j-mongodb4" + open-pull-requests-limit: 10 + schedule: + interval: "daily" + target-branch: "2.x" + registries: + - maven-central + ignore: + # MongoDB 4.x should only upgrade to 4.x + - dependency-name: "org.mongodb:*" + versions: [ "[5,)" ] + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: "daily" + target-branch: "2.x" + + - package-ecosystem: npm + directory: "/" + schedule: + interval: "daily" + target-branch: "2.x" + + - package-ecosystem: maven + directory: "/" + open-pull-requests-limit: 10 + schedule: + interval: "daily" + target-branch: "main" + registries: + - maven-central + ignore: + # Keep Jakarta EE at version 9.0 + - dependency-name: "jakarta.platform:*" + versions: [ "[10,)" ] + # OpenRewrite is quite noisy. Let us skip patch and minor updates: + - dependency-name: "org.openrewrite:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + - dependency-name: "org.openrewrite.maven:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + - dependency-name: "org.openrewrite.recipe:*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + # Plexus Utils 4.x are for Maven 4.x + - dependency-name: "org.codehaus.plexus:plexus-utils" + versions: [ "[4,)" ] + # Don't upgrade to 3.x + - dependency-name: "org.apache.logging.log4j:log4j-api" + versions: [ "[3,)" ] + # The Console Appender only support JANSI 1.x for now + # see https://github.com/apache/logging-log4j2/issues/1736 + - dependency-name: "org.fusesource.jansi:jansi" + update-types: [ "version-update:semver-major" ] + + - package-ecosystem: maven + directories: + - "/log4j-slf4j-impl" + open-pull-requests-limit: 10 + schedule: + interval: "daily" + target-branch: "main" + registries: + - maven-central + ignore: + # SLF4J 1.7.x should only upgrade to 1.7.x and + - dependency-name: "org.slf4j:slf4j-api" + versions: [ "[1,)" ] + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: "daily" + target-branch: "main" + + - package-ecosystem: npm + directory: "/" + schedule: + interval: "daily" + target-branch: "main" diff --git a/.github/generate-email.sh b/.github/generate-email.sh new file mode 100755 index 00000000000..469023ad819 --- /dev/null +++ b/.github/generate-email.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Enable strict mode +set -euo pipefail +IFS=$'\n\t' + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) + +stderr() { + echo "$*" 1>&2 +} + +fail_for_invalid_args() { + stderr "Invalid arguments!" + stderr "Expected arguments: " + exit 1 +} + +# Check arguments +[ $# -ne 4 ] && fail_for_invalid_args + +# Constants +PROJECT_NAME="Apache Log4j" +PROJECT_ID="log4j" +PROJECT_VERSION="$2" +PROJECT_SITE="https://logging.apache.org/$PROJECT_ID" +PROJECT_STAGING_SITE="${PROJECT_SITE/apache.org/staged.apache.org}" +PROJECT_REPO="https://github.com/apache/logging-log4j2" +COMMIT_ID="$3" +NEXUS_URL="$4" +PROJECT_DIST_URL="https://dist.apache.org/repos/dist/dev/logging/$PROJECT_ID/$PROJECT_VERSION" + +# Check release notes file +RELEASE_NOTES_FILE="$SCRIPT_DIR/../target/generated-site/antora/modules/ROOT/pages/_release-notes/$PROJECT_VERSION.adoc" +[ -f "$RELEASE_NOTES_FILE" ] || { + stderr "Couldn't find release notes file: $RELEASE_NOTES_FILE" + exit 1 +} + +dump_release_notes() { + awk "f{print} /^Release date::/{f=1}" "$RELEASE_NOTES_FILE" \ + | sed -r -e 's!'$PROJECT_REPO'/(issues|pull)/[0-9]+\[([0-9]+)\]!#\2!g + s!https://github.com/([^/]+)/([^/]+)/(pull|issues)/([0-9]+)\[(\1/\2#\4)\]!\5!g' +} + +case $1 in + +vote) + cat < [!IMPORTANT] +> Base your changes on `2.x` branch if you are targeting Log4j 2; use `main` otherwise. + +## Checklist + +Before we can review and merge your changes, please go through the checklist below. If you're still working on some items, feel free to submit your pull request as a draft—our CI will help guide you through the remaining steps. + +### ✅ Required checks + +- [ ] **License**: I confirm that my changes are submitted under the [Apache License, Version 2.0](https://apache.org/licenses/LICENSE-2.0). +- [ ] **Commit signatures**: All commits are signed and verifiable. (See [GitHub Docs on Commit Signature Verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)). +- [ ] **Code formatting**: The code is formatted according to the project’s style guide. +
+ How to check and fix formatting + + - To **check** formatting: `./mvnw spotless:check` + - To **fix** formatting: `./mvnw spotless:apply` + + See [the build instructions](https://logging.apache.org/log4j/2.x/development.html#building) for details. +
+- [ ] **Build & Test**: I verified that the project builds and all unit tests pass. +
+ How to build the project + + Run: `./mvnw verify` + + See [the build instructions](https://logging.apache.org/log4j/2.x/development.html#building) for details. +
+ +### 🧪 Tests (select one) + +- [ ] I have added or updated tests to cover my changes. +- [ ] No additional tests are needed for this change. + +### 📝 Changelog (select one) + +- [ ] I added a changelog entry in `src/changelog/.2.x.x`. (See [Changelog Entry File Guide](https://logging.apache.org/log4j/tools/log4j-changelog.html#changelog-entry-file)). +- [ ] This is a trivial change and does not require a changelog entry. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000000..e76d34669d8 --- /dev/null +++ b/.github/workflows/build.yaml @@ -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. +# + +name: build + +on: + push: + branches: + - "2.x" + - "release/2*" + pull_request: + +permissions: read-all + +jobs: + + build: + if: github.actor != 'dependabot[bot]' + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/12.1.1 + secrets: + DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.DEVELOCITY_ACCESS_KEY }} + with: + java-version: | + 8 + 17 + site-enabled: true + reproducibility-check-enabled: false + develocity-enabled: ${{ ! startsWith(github.ref_name, 'release/') }} + + deploy-snapshot: + needs: build + if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' + uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@rel/12.1.1 + # Secrets for deployments + secrets: + NEXUS_USERNAME: ${{ secrets.NEXUS_USER }} + NEXUS_PASSWORD: ${{ secrets.NEXUS_PW }} + with: + java-version: | + 8 + 17 + + deploy-release: + needs: build + if: github.repository == 'apache/logging-log4j2' && startsWith(github.ref_name, 'release/') + uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@rel/12.1.1 + # Secrets for deployments + secrets: + GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} + NEXUS_USERNAME: ${{ secrets.LOGGING_STAGE_DEPLOYER_USER }} + NEXUS_PASSWORD: ${{ secrets.LOGGING_STAGE_DEPLOYER_PW }} + SVN_USERNAME: ${{ secrets.LOGGING_SVN_DEV_USERNAME }} + SVN_PASSWORD: ${{ secrets.LOGGING_SVN_DEV_PASSWORD }} + # Write permissions to allow the Maven `revision` property update, changelog release, etc. + permissions: + contents: write + with: + java-version: | + 8 + 17 + project-id: log4j + + verify-reproducibility: + needs: [ deploy-snapshot, deploy-release ] + if: ${{ always() && (needs.deploy-snapshot.result == 'success' || needs.deploy-release.result == 'success') }} + name: "verify-reproducibility (${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }})" + uses: apache/logging-parent/.github/workflows/verify-reproducibility-reusable.yaml@rel/12.1.1 + with: + nexus-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || 'https://repository.apache.org/content/groups/snapshots' }} + # Encode the `runs-on` input as JSON array + runs-on: '["ubuntu-latest", "macos-latest"]' + + # Run integration-tests automatically after a snapshot or RC is published + integration-test: + needs: [ deploy-snapshot, deploy-release ] + if: ${{ always() && (needs.deploy-snapshot.result == 'success' || needs.deploy-release.result == 'success') }} + name: "integration-test (${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }})" + uses: apache/logging-log4j-samples/.github/workflows/integration-test.yaml@main + with: + log4j-version: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.project-version || needs.deploy-snapshot.outputs.project-version }} + log4j-repository-url: ${{ needs.deploy-release.result == 'success' && needs.deploy-release.outputs.nexus-url || needs.deploy-snapshot.outputs.nexus-url }} + # Use the `main` branch of `logging-log4j-samples` + samples-ref: 'refs/heads/main' diff --git a/.github/workflows/close-stale.yaml b/.github/workflows/close-stale.yaml new file mode 100644 index 00000000000..5978d1aac5d --- /dev/null +++ b/.github/workflows/close-stale.yaml @@ -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. +# + +name: 'Close stale issues' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +permissions: + issues: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + with: + # Labels need to match with the ones in `labeler.yaml`! + only-labels: waiting-for-user + days-before-stale: 60 + days-before-close: 7 + stale-issue-message: > + This issue is stale because it has been waiting for your feedback for more than 60 days. + The Apache Logging Services community values every submitted issue, but without additional information from you, + we are unable to provide a solution to your problem. + + Please comment on this issue or it will be closed in 7 days. diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml new file mode 100644 index 00000000000..6609a93e774 --- /dev/null +++ b/.github/workflows/codeql-analysis.yaml @@ -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. +# + +name: codeql-analysis + +on: + push: + branches: [ "2.x", "main" ] + pull_request: + branches: [ "2.x", "main" ] + schedule: + - cron: '32 12 * * 5' + +permissions: read-all + +jobs: + + analyze: + uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@rel/12.1.1 + with: + java-version: | + 8 + 17 + # Permissions required to publish Security Alerts + permissions: + actions: read + contents: read + security-events: write diff --git a/.github/workflows/deploy-site.yaml b/.github/workflows/deploy-site.yaml new file mode 100644 index 00000000000..e65aab28caa --- /dev/null +++ b/.github/workflows/deploy-site.yaml @@ -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. +# + +name: deploy-site + +on: + push: + branches: + - "2.x" + - "2.x-site-pro" + - "release/2*" + paths-ignore: + - "**.md" + - "**.txt" + +permissions: read-all + +jobs: + + deploy-site-stg: + if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.1.1 + # Secrets for committing the generated site + secrets: + GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} + # Write permissions for committing the generated site + permissions: + contents: write + with: + asf-yaml-content: | + staging: + profile: ~ + whoami: ${{ github.ref_name }}-site-stg-out + subdir: content/log4j/2.x + install-required: true + target-branch: ${{ github.ref_name }}-site-stg-out + + deploy-site-pro: + if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x-site-pro' + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.1.1 + # Secrets for committing the generated site + secrets: + GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} + # Write permissions for committing the generated site + permissions: + contents: write + with: + asf-yaml-content: | + publish: + whoami: ${{ github.ref_name }}-out + subdir: content/log4j/2.x + install-required: true + target-branch: ${{ github.ref_name }}-out + + export-version: + if: github.repository == 'apache/logging-log4j2' && startsWith(github.ref_name, 'release/') + runs-on: ubuntu-latest + outputs: + version: ${{ steps.export-version.outputs.version }} + steps: + - name: Export version + id: export-version + run: | + version=$(echo "${{ github.ref_name }}" | sed 's/^release\///') + echo "version=$version" >> "$GITHUB_OUTPUT" + + deploy-site-rel: + needs: export-version + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/12.1.1 + # Secrets for committing the generated site + secrets: + GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} + # Write permissions for committing the generated site + permissions: + contents: write + with: + asf-yaml-content: | + staging: + profile: ~ + whoami: ${{ github.ref_name }}-site-stg-out + subdir: content/log4j/${{ needs.export-version.outputs.version }} + install-required: true + target-branch: ${{ github.ref_name }}-site-stg-out diff --git a/.github/workflows/develocity-publish-build-scans.yaml b/.github/workflows/develocity-publish-build-scans.yaml new file mode 100644 index 00000000000..2a87945214b --- /dev/null +++ b/.github/workflows/develocity-publish-build-scans.yaml @@ -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. +# +name: Develocity - Publish Maven Build Scans + +on: + workflow_run: + workflows: [ "build" ] + types: [ completed ] + +jobs: + + publish-build-scans: + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + steps: + + - name: Setup Build Scan link capture + uses: gradle/develocity-actions/setup-maven@4a2aed82eea165ba2d5c494fc2a8730d7fdff229 # 1.4 + with: + capture-build-scan-links: true + + - name: Publish Build Scans + uses: gradle/develocity-actions/maven-publish-build-scan@4a2aed82eea165ba2d5c494fc2a8730d7fdff229 # 1.4 + with: + develocity-url: 'https://develocity.apache.org' + develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml new file mode 100644 index 00000000000..141ad42a55b --- /dev/null +++ b/.github/workflows/labeler.yaml @@ -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. +# + +name: 'Check labels' +on: + issue_comment: + types: [created] + issues: + types: [opened] +env: + issue_user_type: >- + ${{ + contains(fromJSON('["COLLABORATOR","CONTRIBUTOR","MEMBER","OWNER"]'), github.event.issue.author_association) && 'maintainer' || 'user' + }} + comment_user_type: >- + ${{ + github.event_name == 'issue_comment' && + ( + contains(fromJSON('["COLLABORATOR","CONTRIBUTOR","MEMBER","OWNER"]'), github.event.comment.author_association) && 'maintainer' || 'user' + ) || null + }} + +permissions: + issues: write + +jobs: + check-labels: + # Prevents the job from running on pull requests + if: ${{ ! github.event.issue.pull_request }} + runs-on: ubuntu-latest + + steps: + + - name: Print user type + env: + ISSUE_AUTHOR: ${{ github.event.issue.author_association }} + COMMENT_AUTHOR: ${{ github.event.comment.author_association }} + EVENT_NAME: ${{ github.event_name }} + ISSUE_USER_TYPE: ${{ env.issue_user_type }} + COMMENT_USER_TYPE: ${{ env.comment_user_type }} + run: | + echo "Event name:" $EVENT_NAME + echo "Issue created by:" $ISSUE_USER_TYPE + echo "Issue author association:" $ISSUE_AUTHOR + echo "Comment created by:" $COMMENT_USER_TYPE + echo "Comment author association:" $COMMENT_AUTHOR + + # The `waiting-for-maintainer` label needs to match with the one in `close-stale.yaml`! + - name: Add `waiting-for-maintainer` label + if: github.event_name == 'issues' && env.issue_user_type == 'user' || env.comment_user_type == 'user' + env: + ISSUE: ${{ github.event.issue.html_url }} + GH_TOKEN: ${{ github.token }} + run: | + gh issue edit $ISSUE --add-label 'waiting-for-maintainer' --remove-label 'waiting-for-user' diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml new file mode 100644 index 00000000000..134d18b1a0e --- /dev/null +++ b/.github/workflows/merge-dependabot.yaml @@ -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. +# + +name: merge-dependabot + +on: + pull_request_target: + paths-ignore: + - "**.adoc" + - "**.md" + - "**.txt" + +permissions: read-all + +jobs: + + build: + if: github.repository == 'apache/logging-log4j2' && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/12.1.1 + secrets: + DV_ACCESS_TOKEN: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + with: + java-version: | + 8 + 17 + develocity-enabled: true + reproducibility-check-enabled: false + + merge-dependabot: + needs: build + uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/12.1.1 + with: + java-version: 17 + permissions: + contents: write # to push changelog commits + pull-requests: write # to close the PR + secrets: + GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} # to sign commits diff --git a/.gitignore b/.gitignore index fce0e91bb57..2f5bcffaf8e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,14 @@ .project .idea **/*.iml +.vscode +/.metadata/ +/.mvn/wrapper/maven-wrapper.jar +/.mvn/.develocity +.flattened-pom.xml +.project +.settings +.surefire-* **/target target/ .settings @@ -24,6 +32,13 @@ target/ .cache-tests .toDelete .factorypath +.surefire-* velocity.log felix-cache/ -bin/ +/.metadata/ +.DS_Store +node +node_modules +package-lock.json +# Maven extensions +/.mvn/extensions.xml diff --git a/.java-version b/.java-version new file mode 100644 index 00000000000..98d9bcb75a6 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +17 diff --git a/.logging-parent-bom-activator b/.logging-parent-bom-activator new file mode 100644 index 00000000000..e7980a6d13b --- /dev/null +++ b/.logging-parent-bom-activator @@ -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. +## +This file activates the `flatten-bom` profile. \ No newline at end of file diff --git a/.mvn/develocity.xml b/.mvn/develocity.xml new file mode 100644 index 00000000000..caa112766a0 --- /dev/null +++ b/.mvn/develocity.xml @@ -0,0 +1,30 @@ + + + logging-log4j2 + + https://develocity.apache.org + + + + true + true + + + 0.0.0.0 + + + + + + + false + + + + false + + + false + + + diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000000..f3b9f7db5d4 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,11 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED +-Dfile.encoding=UTF-8 diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index c6feb8bb6f7..00000000000 Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index d183696ac15..c76af40b2f9 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,16 +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. - -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 +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionSha256Sum=8351955a9acf2f83c136c4eee0f6db894ab6265fdbe0a94b32a380307dbaa3e1 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +wrapperVersion=3.3.2 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.adoc b/BUILDING.adoc new file mode 100644 index 00000000000..9b25abfa912 --- /dev/null +++ b/BUILDING.adoc @@ -0,0 +1,230 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + +[#requirements] +== Requirements + +* JDK 17+ +* A modern Linux, OSX, or Windows host + +[#building] +== Building the sources + +You can build and verify the sources as follows: + +[source,bash] +---- +./mvnw verify +---- + +`verify` goal runs validation and test steps next to building (i.e., compiling) the sources. +To speed up the build, you can skip verification: + +[source,bash] +---- +./mvnw -DskipTests package +---- + +If you want to install generated artifacts to your local Maven repository, replace above `verify` and/or `package` goals with `install`. + +[#dns] +=== DNS lookups in tests + +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()`. +To remedy this, you can execute the following: + +[source,bash] +---- +printf '127.0.0.1 %s\n::1 %s\n' `hostname` `hostname` | sudo tee -a /etc/hosts +---- + +[#java8-tests] +=== Java 8 tests + +You can run tests using the target JRE (i.e., JRE 8) as follows: + +[#toolchains] +. Maven Toolchains is used to employ additional JDKs required for tests. +You either need to have a user-level configuration in `~/.m2/toolchains.xml` or explicitly provide one to the Maven: `./mvnw --global-toolchains /path/to/toolchains.xml`. ++ +.An example `toolchains.xml` containing a JDK 8 toolchain +[source,xml] +---- + + + + jdk + + 1.8.0_372 + + + /usr/lib/jvm/java-8-openjdk-amd64 + + + +---- + +. Run Maven tests with the `java8-tests` profile: ++ +[source,bash] +---- +./mvnw verify -Pjava8-tests,!java8-incompat-fixes +---- + +[#docker] +=== Docker tests + +Certain tests use Docker to spawn necessary external services. +Docker tests are configured using the `docker` Maven profile, which is activated by default for the CI environment. +You can locally enable this profile by passing a `-P docker` argument to your `./mvnw` commands. + +[#website] +== Building the website + +You can build the website as follows: + +[source,bash] +---- +./mvnw compile # <1> +./mvnw site # <2> +---- +<1> Generate plugin descriptors that will be used to generate the plugin reference page. +Descriptors are placed under `target/plugin-descriptors`. +<2> Generate the website to `target/site` + +You can view the generated website with a browser by pointing it to `target/site` directory. + +[#development] +== Development + +You can follow below steps for casual development needs: + +. Make sure you installed everything: ++ +[source,bash] +---- +./mvnw install -DskipTests +---- + +. After making a change to, say, `log4j-core`, install your changes: ++ +[source,bash] +---- +./mvnw install -pl :log4j-core -DskipTests +---- + +. Run all tests associated with `log4j-core`: ++ +[source,bash] +---- +./mvnw test -pl :log4j-core-test +---- + +. Run a particular test: ++ +[source,bash] +---- +./mvnw test -pl :log4j-core-test -Dtest=FooBarTest +---- + +. Make sure all build checks (Spotless, Spotbugs, BND, RAT, etc.) succeed: ++ +[source,bash] +---- +./mvnw verify -DskipTests -pl :log4j-core,:log4j-core-test +---- + +[#development-ide-debug] +=== Debugging `./mvnw test` with IDE + +You can connect your IDE to a `./mvnw test` run by + +. Run `./mvnw test -pl :log4j-core-test -Dtest=FooBarTest -Dmaven.surefire.debug` +. Use _"Run > Attach to process"_ in IntelliJ IDEA + +[#development-ci] +=== Activating CI profiles + +There are certain Maven profiles activated only for CI. +As a consequence of this, you can observe certain CI failures that you can't reproduce locally. +To work around this, you can activate CI profiles by running Maven commands as follows: + +[source,bash] +---- +CI=true ./mvnw ... +---- + +[#development-faq-idea-plugin-not-found] +=== Compilation in IntelliJ IDEA fails with `java: plug-in not found: ErrorProne` + +Try removing all _"Override compiler parameters per-module"_ entries in _"Settings > Build, Execution, Deployment > Compiler > Java Compiler"_. + +[#development-api-compatibility] +=== Fixing API compatibility check failures + +Log4j uses the +https://github.com/bndtools/bnd/tree/master/maven-plugins/bnd-baseline-maven-plugin[BND Baseline Maven Plugin] +to enforce its +https://semver.org/[semantic versioning policy]. +Following the +https://bnd.bndtools.org/chapters/170-versioning.html#best-practices[OSGi versioning best practices], both Log4j artifacts and packages are versioned. +If you modify Log4j's public API, a BND Baseline error like the following will occur: + +[source] +---- + Name Type Delta New Old Suggest If Prov. + org.apache.logging.foo PACKAGE UNCHANGED 2.1.0 2.1.0 ok - +* org.apache.logging.foo.bar PACKAGE MINOR 2.3.4 2.3.4 2.4.0 - + MINOR PACKAGE org.apache.logging.foo.bar + ... +---- + +The solution of the error depends on the change kind (`Delta`): + +[#development-api-compatibility-micro] +`MICRO`:: ++ +For patch level changes modify the `package-info.java` file of the `org.apache.logging.foo.bar` package and update the `@Version` annotation to the number suggested by BND: increment just the patch number. ++ +[source,java] +---- +@Version("2.3.5") +package org.apache.logging.foo.bar; +---- + +[#development-api-compatibility-minor] +`MINOR`:: ++ +Changes of type `MINOR` require more work: ++ +-- +* Make sure that the current Log4j version is a minor upgrade over the last released version. +If that is not the case (e.g., it is `2.34.5-SNAPSHOT`) modify the `` property in the main POM file (e.g., change it to `2.35.0-SNAPSHOT`). +* Make sure to add a +https://logging.apache.org/log4j/tools/log4j-changelog.html#changelog-entry-file[changelog entry] +of type `added`, `changed` or `deprecated` to your PR. +* As in the +<> +modify the `package-info.java` file of the package and update the `@Version` annotation to the **next Log4j version** (`2.35.0` in the example) and **not** the minimal version number suggested by BND. +The purpose of this policy is to have an alignment between package versions and the last Log4j version, where an API change occurred. +-- + +[#development-api-compatibility-major] +`MAJOR`:: ++ +Changes of type `MAJOR` (i.e. breaking changes) are not accepted in the `2.x` branch. +If you believe it is not a breaking change (e.g., you removed a class **documented** as private), the development team will guide you on how to solve it. diff --git a/BUILDING.md b/BUILDING.md deleted file mode 100644 index d9734588124..00000000000 --- a/BUILDING.md +++ /dev/null @@ -1,77 +0,0 @@ - -# 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. - -``` -[Macintosh] -t ./toolchains-sample-mac.xml -[Windows] -t ./toolchains-sample-win.xml -[Linux] -t ./toolchains-sample-linux.xml -``` - -To perform the license release audit, a.k.a. "RAT check", run. - - mvn apache-rat:check - -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`. - -To install the jars in your local Maven repository, from a command line, run: - - mvn clean install - -Once install is run, you can run the Clirr check on the API and 1.2 API modules: - - mvn clirr:check -pl log4j-api - - mvn clirr:check -pl log4j-1.2-api - -Next, to build the site: - -If Java 7 runs out of memory building the site, you will need: - - set MAVEN_OPTS=-Xmx2000m -XX:MaxPermSize=384m - - 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 . diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc new file mode 100644 index 00000000000..29a20e8bc84 --- /dev/null +++ b/CODE_OF_CONDUCT.adoc @@ -0,0 +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. +//// + +See https://www.apache.org/foundation/policies/conduct.html[the Apache Software Foundation's Code of Conduct]. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 526d871feaf..00000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,70 +0,0 @@ - - -# Contributing to Apache Log4j 2 - -You have found a bug or you have an idea for a cool new feature? Contributing code is a great way to give something back to -the open source community. Before you dig right into the code there are a few guidelines that we need contributors to -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). -+ 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. - + Make sure you fill in the earliest version that you know has the issue. -+ Fork the repository on GitHub. - -## Making Changes - -+ Create a topic branch from where you want to base your work (this is usually the master branch). -+ Make commits of logical units. -+ Respect the original code style: - + Only use spaces for indentation. - + Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change. - + Check for unnecessary whitespace with git diff --check before committing. -+ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue. -+ Make sure you have added the necessary tests for your changes. -+ Run all the tests with `mvn clean verify` to assure nothing else was accidentally broken. - -## Making Trivial Changes - -For changes of a trivial nature to comments and documentation, it is not always necessary to create a new ticket in JIRA. -In this case, it is appropriate to start the first line of a commit with '(doc)' instead of a ticket number. - -## Submitting Changes - -+ Sign the [Contributor License Agreement][cla] if you haven't already. -+ Push your changes to a topic branch in your fork of the repository. -+ Submit a pull request to the repository in the apache organization. -+ Update your JIRA ticket and include a link to the pull request in the ticket. - -## Additional Resources - -+ [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) -+ [Contributor License Agreement][cla] -+ [General GitHub documentation](https://help.github.com/) -+ [GitHub pull request documentation](https://help.github.com/send-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/FUZZING.adoc b/FUZZING.adoc new file mode 100644 index 00000000000..838a89419a0 --- /dev/null +++ b/FUZZING.adoc @@ -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. +//// + +Log4j contains fuzz tests implemented using https://github.com/CodeIntelligenceTesting/jazzer[Jazzer]footnote:[ +We are aware that https://github.com/google/oss-fuzz/discussions/12195[Jazzer is discontinued]. +Yet it is still the only mature fuzzing framework in Java and https://google.github.io/oss-fuzz/getting-started/new-project-guide/jvm-lang/#jazzer[the recommended library by OSS-Fuzz].]. +These tests are located in `-fuzz-test` prefixed modules; `log4j-core-fuzz-test`, `log4j-layout-template-json-fuzz-test`, etc. + +[#oss-fuzz] +== Google OSS-Fuzz + +https://github.com/google/oss-fuzz[OSS-Fuzz] is a Google service that continuously runs fuzz tests of critical F/OSS projects on a beefy cluster and reports its findings (bugs, vulnerabilities, etc.) privately to project maintainers. +Log4j provides OSS-Fuzz integration with following helpers: + +- https://github.com/google/oss-fuzz/tree/master/projects/log4j2/Dockerfile[Dockerfile] to create a container image for running tests +- link:oss-fuzz-build.sh[`oss-fuzz-build.sh`] to generate fuzz test runner scripts along with all necessary dependencies + +[#faq] +== F.A.Q. + +Below we will try to answer some frequently asked questions. + +[#running] +=== How can I run fuzz tests locally? + +. Clone the OSS-Fuzz repository: ++ +[source,bash] +---- +git clone --depth 1 https://github.com/google/oss-fuzz google-oss-fuzz && cd $_ +---- + +. Build the container image: ++ +[source,bash] +---- +python infra/helper.py build_image log4j2 +---- + +. Run the container image to build the Log4j project and generate runner scripts along with dependencies: ++ +[source,bash] +---- +python infra/helper.py build_fuzzers \ + --sanitizer address --engine libfuzzer --architecture x86_64 \ + log4j2 +---- + +. List generated runner scripts: ++ +[source,bash] +---- +ls -al build/out/log4j2 +---- + +. Check one of the generated runner scripts: ++ +[source,bash] +---- +python infra/helper.py check_build \ + --sanitizer address --engine libfuzzer --architecture x86_64 \ + log4j2 log4j-core-fuzz-test-PatternLayoutFuzzer +---- + +. Execute one of the generated runner scripts: ++ +[source,bash] +---- +python infra/helper.py run_fuzzer \ + --sanitizer address --engine libfuzzer --architecture x86_64 \ + log4j2 log4j-core-fuzz-test-PatternLayoutFuzzer +---- + +[#view] +=== How can I view fuzzing failures detected by OSS-Fuzz? + +The system running fuzzers registered to OSS-Fuzz is called *ClusterFuzz*, which provides https://oss-fuzz.com/[a web interface] for maintainers to monitor the fuzzing results. +Tests outputs and <<#reproduce,reproduction>> inputs for failed tests are stored in https://console.cloud.google.com/storage/browser/log4j2-logs.clusterfuzz-external.appspot.com[a Google Cloud Storage bucket]. +Access to both the web interface and the bucket is restricted, and only allowed to https://github.com/google/oss-fuzz/blob/master/projects/log4j2/project.yaml[those configured for the project]. + +[#reproduce] +=== How can I reproduce fuzzing failures detected by OSS-Fuzz? + +Download the associated `.testcase` file from https://console.cloud.google.com/storage/browser/log4j2-logs.clusterfuzz-external.appspot.com[the Google Cloud Storage bucket], and run the following command: + +[source,bash] +---- +python infra/helper.py reproduce \ + log4j2 +---- + +Refer to https://google.github.io/oss-fuzz/advanced-topics/reproducing/[the related OSS-Fuzz documentation] for details. diff --git a/NOTICE.txt b/NOTICE.txt index bd95322f254..0c37b52bfe7 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Log4j -Copyright 1999-2017 Apache Software Foundation +Copyright 1999-2024 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.adoc b/README.adoc new file mode 100644 index 00000000000..d7d9bd39f6d --- /dev/null +++ b/README.adoc @@ -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. +//// + +https://github.com/apache/logging-log4j2/actions/workflows/build.yaml[image:https://img.shields.io/github/actions/workflow/status/apache/logging-log4j2/build.yaml?branch=2.x&label=build%20%282.x%29[Build (2.x)]] +https://search.maven.org/artifact/org.apache.logging.log4j/log4j-api[image:https://img.shields.io/maven-central/v/org.apache.logging.log4j/log4j-api?versionPrefix=2.[Maven Central (2.x)]] +https://github.com/apache/logging-log4j2/security/code-scanning[image:https://github.com/apache/logging-log4j2/actions/workflows/codeql-analysis.yaml/badge.svg?branch=2.x[CodeQL (2.x)]] + +Apache Log4j is a versatile, industrial-grade Java logging framework composed of an API, its implementation, and components to assist the deployment for various use cases. +For further information (support, download, etc.) see https://logging.apache.org/log4j[the project website]. + +[#development] +== Development + +Looking for *build instructions*, branching scheme, and other developer resources? +See https://logging.apache.org/log4j/2.x/development.html[the Development page] at the website. diff --git a/README.md b/README.md deleted file mode 100644 index e5ade8a1c62..00000000000 --- a/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# [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, -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) - - -## Pull Requests on Github - -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. - -## Usage - -Users should refer to [Maven, Ivy, Gradle, and SBT Artifacts](http://logging.apache.org/log4j/2.x/maven-artifacts.html) -on the Log4j web site for instructions on how to include Log4j into their project using their chosen build tool. - -Basic usage of the `Logger` API: - -```java -package com.example; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -public class Example { - private static final Logger LOGGER = LogManager.getLogger(); - - public static void main(String... args) { - String thing = args.length > 0 ? args[0] : "world"; - LOGGER.info("Hello, {}!", thing); - LOGGER.debug("Got calculated value only if debug enabled: {}", () -> doSomeCalculation()); - } - - private static Object doSomeCalculation() { - // do some complicated calculation - } -} -``` - -And an example `log4j2.xml` configuration file: - -```xml - - - - - - - - - - - - - - -``` - -## Documentation - -The Log4j 2 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. -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). - -## 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`, -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 -[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 -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 -``` - -## Contributing - -We love contributions! Take a look at -[our contributing page](https://github.com/apache/logging-log4j2/blob/master/CONTRIBUTING.md). diff --git a/RELEASE-NOTES.adoc b/RELEASE-NOTES.adoc new file mode 100644 index 00000000000..65180c6b265 --- /dev/null +++ b/RELEASE-NOTES.adoc @@ -0,0 +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. +//// + +See https://logging.apache.org/log4j/2.x/release-notes.html[the Release Notes page]. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md deleted file mode 100644 index fe5f8001863..00000000000 --- a/RELEASE-NOTES.md +++ /dev/null @@ -1,166 +0,0 @@ -# Apache Log4j 2.10.0 Release Notes - -The Apache Log4j 2 team is pleased to announce the Log4j 2.10.0 release! - -Apache Log4j is a well known framework for logging application behavior. Log4j 2 is an upgrade -to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides -many other modern features such as support for Markers, lambda expressions for lazy logging, -property substitution using Lookups, multiple patterns on a PatternLayout and asynchronous -Loggers. Another notable Log4j 2 feature is the ability to be "garbage-free" (avoid allocating -temporary objects) while logging. In addition, Log4j 2 will not lose events while reconfiguring. - -This release contains new features, bugfixes and minor enhancements. Some of the new features include support -for the Java 9 module system, support for the new SLF4j 1.8 binding mechanism, simplification of the Log4j -property naming scheme, and native support of Jetty's logger. Log4j API is now a fully compliant module -while the other Log4j jars are named automatic modules. - -As of Log4j 2.9.0, the Log4j API was modified to use java.util.ServiceLoader to locate Log4j implementations, -although the former binding mechanism is still supported. The Log4j API jar is now a multi-release jar -to provide implementations of Java 9 specific classes. Multi-release jars are not supported by -the OSGi specification so OSGi modules will not be able to take advantage of these implementations -but will not lose functionality as they will fall back to the implementations used in Java 7 and 8. -More details on the new features and fixes are itemized below. Note that some tools are not compatible -with multi-release jars and may fail trying to process class files in the META-INF/versions/9 folder. -Those errors should be reported to the tool vendor. - -Note that subsequent to the 2.9.0 release, for security reasons, SerializedLayout is deprecated and no -longer used as default in the Socket and JMS appenders. SerializedLayout can still be used as before, -but has to be specified explicitly. To retain old behaviour, you have to change configuration like: - - - - - -into: - - - - - - - -We do, however, discourage the use of SerializedLayout and recommend JsonLayout as a replacement: - - - - - - - -Note that subsequent to the 2.9.0 release, for security reasons, Log4j does not process DTD in XML files. -If you used DTD for including snippets, you have to use XInclude or Composite Configuration instead. - -The Log4j 2.10.0 API, as well as many core components, maintains binary compatibility with previous releases. - -## GA Release 2.10.0 - -Changes in this version include: - -### New Features -* [LOG4J2-2120](https://issues.apache.org/jira/browse/LOG4J2-2120): -Properly escape newlines and other control characters in JSON. Thanks to Carter Douglas Kozak. -* [LOG4J2-2109](https://issues.apache.org/jira/browse/LOG4J2-2109): -Add property to disable message pattern converter lookups. Thanks to Carter Douglas Kozak. -* [LOG4J2-2112](https://issues.apache.org/jira/browse/LOG4J2-2112): -MapMessage should use deep toString for values. Thanks to Carter Douglas Kozak. -* [LOG4J2-2103](https://issues.apache.org/jira/browse/LOG4J2-2103): -XML encoding for PatternLayout. -* [LOG4J2-2114](https://issues.apache.org/jira/browse/LOG4J2-2114): -Provide a native Log4j 2 implementation of Eclipse Jetty's org.eclipse.jetty.util.log.Logger. -* [LOG4J2-1203](https://issues.apache.org/jira/browse/LOG4J2-1203): -Allow filtering of line breaks in layout pattern. Thanks to Robert Turner. -* [LOG4J2-2098](https://issues.apache.org/jira/browse/LOG4J2-2098): -Add a noop AppenderSkeleton for applications still using Log4j 1.x. -* [LOG4J2-2062](https://issues.apache.org/jira/browse/LOG4J2-2062): -Add possibility of sending the key of a message to Kafka using KafkaAppender. Thanks to Jorge Sanchez. -* [LOG4J2-2056](https://issues.apache.org/jira/browse/LOG4J2-2056): -Modularize Log4j-api and make most other log4j jars automatic modules. -* [LOG4J2-1431](https://issues.apache.org/jira/browse/LOG4J2-1431): -Simplify log4j system property naming scheme. -* [LOG4J2-1809](https://issues.apache.org/jira/browse/LOG4J2-1809): -Add global configuration environment SPI. -* [LOG4J2-1694](https://issues.apache.org/jira/browse/LOG4J2-1694): -Add fields with fixed values to JSON/XML/YAML layouts. Thanks to Michal Dvořák. -* [LOG4J2-2054](https://issues.apache.org/jira/browse/LOG4J2-2054): -Provide ways to configure SSL that avoid plain-text passwords in the log4j configuration. The configuration may - now specify a system environment variable that holds the password, or the path to a file that holds the password. -* [LOG4J2-2071](https://issues.apache.org/jira/browse/LOG4J2-2071): -Add org.apache.logging.log4j.core.config.composite.CompositeConfiguration#toString(). Thanks to Carter Kozak. - -### Fixed Bugs -* [LOG4J2-2107](https://issues.apache.org/jira/browse/LOG4J2-2107): -MapMessage supports both StringBuilderFormattable and MultiformatMessage. Thanks to Carter Douglas Kozak. -* [LOG4J2-2102](https://issues.apache.org/jira/browse/LOG4J2-2102): -MapMessage JSON encoding will escape keys and values. Thanks to Carter Douglas Kozak. -* [LOG4J2-2101](https://issues.apache.org/jira/browse/LOG4J2-2101): -Non-string value in MapMessage caused ClassCastException. Thanks to Carter Douglas Kozak. -* [LOG4J2-2091](https://issues.apache.org/jira/browse/LOG4J2-2091): -Log4j respects the configured "log4j2.is.webapp" property Thanks to Carter Douglas Kozak. -* [LOG4J2-2100](https://issues.apache.org/jira/browse/LOG4J2-2100): -LevelMixIn class for Jackson is coded incorrectly -* [LOG4J2-2087](https://issues.apache.org/jira/browse/LOG4J2-2087): -Jansi now needs to be enabled explicitly (by setting system property `log4j.skipJansi` to `false`). To avoid causing problems for web applications, Log4j will no longer automatically try to load Jansi without explicit configuration. Thanks to Andy Gumbrecht. -* [LOG4J2-2060](https://issues.apache.org/jira/browse/LOG4J2-2060): -AbstractDatabaseManager should make a copy of LogEvents before holding references to them: AsyncLogger log events are mutable. -* [LOG4J2-2055](https://issues.apache.org/jira/browse/LOG4J2-2055): -If Log4j is used as the Tomcat logging implementation startup might fail if an application also uses Log4j. -* [LOG4J2-2031](https://issues.apache.org/jira/browse/LOG4J2-2031): -Until this change, messages appeared out of order in log file any time when the async logging queue was full. - With this change, messages are only logged out of order to prevent deadlock when Log4j2 detects recursive - logging while the queue is full. -* [LOG4J2-2053](https://issues.apache.org/jira/browse/LOG4J2-2053): -Exception java.nio.charset.UnsupportedCharsetException: cp65001 in 2.9.0. -* [LOG4J2-1216](https://issues.apache.org/jira/browse/LOG4J2-1216): -Nested pattern layout options broken. Thanks to Thies Wellpott, Barna Zsombor Klara, GFriedrich. -* [LOG4J2-2070](https://issues.apache.org/jira/browse/LOG4J2-2070): -Log4j1XmlLayout does not provide the entire stack trace, it is missing the caused by information. Thanks to Doug Hughes. -* [LOG4J2-2036](https://issues.apache.org/jira/browse/LOG4J2-2036): -CompositeConfiguration supports Reconfiguration. PR #115. Thanks to Robert Haycock. -* [LOG4J2-2073](https://issues.apache.org/jira/browse/LOG4J2-2073): -Log4j-config.xsd should make AppenderRef optional for each Logger element. Thanks to Patrick Lucas. -* [LOG4J2-2074](https://issues.apache.org/jira/browse/LOG4J2-2074): -The console appender should say why it cannot load JAnsi. -* [LOG4J2-2085](https://issues.apache.org/jira/browse/LOG4J2-2085): -Wrong Apache Commons CSV version referenced in the Javadoc of CsvParameterLayout. Thanks to István Neuwirth. - -### Changes -* [LOG4J2-2076](https://issues.apache.org/jira/browse/LOG4J2-2076): -Split up log4j-nosql into one module per appender. -* [LOG4J2-2088](https://issues.apache.org/jira/browse/LOG4J2-2088): -Upgrade picocli to 2.0.3 from 0.9.8. -* [LOG4J2-2025](https://issues.apache.org/jira/browse/LOG4J2-2025): -Provide support for overriding the Tomcat Log class in Tomcat 8.5+. -* [LOG4J2-2057](https://issues.apache.org/jira/browse/LOG4J2-2057): -Support new SLF4J binding mechanism introduced in SLF4J 1.8. -* [LOG4J2-2052](https://issues.apache.org/jira/browse/LOG4J2-2052): -Disable thread name caching by default when running on Java 8u102 or later. -* [LOG4J2-1896](https://issues.apache.org/jira/browse/LOG4J2-1896): -Update classes in org.apache.logging.log4j.core.net.ssl in APIs from String to a PasswordProvider producing - char[] for passwords. -* [LOG4J2-2078](https://issues.apache.org/jira/browse/LOG4J2-2078): -Update LMAX disruptor from 3.3.6 to 3.3.7. -* [LOG4J2-2081](https://issues.apache.org/jira/browse/LOG4J2-2081): -Update Apache Commons Compress from 1.14 to 1.15. -* [LOG4J2-2089](https://issues.apache.org/jira/browse/LOG4J2-2089): -[TagLib] Update servlet-api provided dependency from 2.5 to 3.0.1. -* [LOG4J2-2096](https://issues.apache.org/jira/browse/LOG4J2-2096): -Update Apache Kafka kafka-clients from 0.11.0.1 to 1.0.0. -* [LOG4J2-2077](https://issues.apache.org/jira/browse/LOG4J2-2077): -Update from Jackson 2.9.1 to 2.9.2. -* [LOG4J2-2117](https://issues.apache.org/jira/browse/LOG4J2-2117): -Jackson dependencies for 2.9.2 incorrectly bring in jackson-annotations 2.9.0 instead of 2.9.2. - ---- - -Apache Log4j 2.10.0 requires a minimum of Java 7 to build and run. Log4j 2.3 was the -last release that supported Java 6. - -Basic compatibility with Log4j 1.x is provided through the log4j-1.2-api component, however it -does not implement some of the very implementation specific classes and methods. The package -names and Maven groupId have been changed to org.apache.logging.log4j to avoid any conflicts -with log4j 1.x. - -For complete information on Apache Log4j 2, including instructions on how to submit bug -reports, patches, or suggestions for improvement, see the Apache Apache Log4j 2 website: - -https://logging.apache.org/log4j/2.x/ \ No newline at end of file diff --git a/SECURITY.adoc b/SECURITY.adoc new file mode 100644 index 00000000000..23a4e5b12c0 --- /dev/null +++ b/SECURITY.adoc @@ -0,0 +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. +//// + +See https://logging.apache.org/security.html[the Security page]. diff --git a/antora-playbook.yaml b/antora-playbook.yaml new file mode 100644 index 00000000000..f46037c1a9f --- /dev/null +++ b/antora-playbook.yaml @@ -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. +# + +site: + title: Apache Log4j + url: "https://logging.apache.org/log4j/2.x" + start_page: "ROOT::index.adoc" + +content: + sources: + - url: . + branches: HEAD + start_paths: + - target/generated-site/antora + edit_url: + +runtime: + log: + # Do not fail on errors until we upgrade to `log4j-docgen` 0.10.0, + # which has support for table captions. + failure_level: fatal + +asciidoc: + attributes: + # JSON Template Layout manual page has a deep sectioning, support it. + # The trailing `@` is added so the attribute can still can be overridden in the header of a page. + # For instance, we override this value in release notes page. + page-toclevels: "4@" + log4j-docgen-descriptor-directory: target/plugin-descriptors + log4j-docgen-type-filter-exclude-pattern: ^java\..+ + log4j-docgen-type-target-template: | + #{{{replaceAll sourcedType.groupId "." "-"}}}_{{{replaceAll sourcedType.artifactId "." "-"}}}_{{{replaceAll sourcedType.type.className "." "-"}}} + tabs-sync-option: true + # Force Kroki to download images at build time + kroki-fetch-diagram: true + extensions: + - "@asciidoctor/tabs" + - asciidoctor-kroki + - src/docgen/apiref-macro.js + +ui: + + bundle: + url: "https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable" + snapshot: true + + # Template files: https://github.com/asciidoctor/asciidoctor-docs-ui/blob/main/src + # Template variables: https://docs.antora.org/antora-ui-default/templates + supplemental_files: + + # Add `@asciidoctor/tabs` extension styles + - path: css/vendor/tabs.css + contents: ./node_modules/@asciidoctor/tabs/dist/css/tabs.css + + # Add `@asciidoctor/tabs` extension scripts + - path: js/vendor/tabs.js + contents: ./node_modules/@asciidoctor/tabs/dist/js/tabs.js + + - path: partials/footer-scripts.hbs + contents: | + + + + + + + + + {{#if env.SITE_SEARCH_PROVIDER}} + {{> search-scripts}} + {{/if}} + + - path: partials/head-styles.hbs + contents: | + + + + + + + - path: partials/header-content.hbs + contents: | +
+ +
+ + - path: partials/footer-content.hbs + contents: | +
+

+ Copyright © 1999-{{{year}}} The Apache Software Foundation. + Licensed under the Apache Software License, Version 2.0. + Please read our privacy policy. +

+

+ Apache, Log4j, and the Apache feather logo are trademarks or registered trademarks of The Apache Software Foundation. + Oracle and Java are registered trademarks of Oracle and/or its affiliates. + Other names may be trademarks of their respective owners. +

+
+ + # Disable component version selector + - path: partials/nav-explore.hbs + contents: "" + + # Fix the `Edit this page` link + - path: partials/edit-this-page.hbs + contents: | + diff --git a/checkstyle-header.txt b/checkstyle-header.txt deleted file mode 100644 index 4f33236203a..00000000000 --- a/checkstyle-header.txt +++ /dev/null @@ -1,16 +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. - */ diff --git a/checkstyle-import-control.xml b/checkstyle-import-control.xml deleted file mode 100644 index 8d9a0d07994..00000000000 --- a/checkstyle-import-control.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml deleted file mode 100644 index fd1ad789b31..00000000000 --- a/checkstyle-suppressions.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index 80ad14d34e0..00000000000 --- a/checkstyle.xml +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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/.log4j-plugin-processing-activator b/log4j-1.2-api/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-1.2-api/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-1.2-api/pom.xml b/log4j-1.2-api/pom.xml index 3ddf8055ac7..d67bb934513 100644 --- a/log4j-1.2-api/pom.xml +++ b/log4j-1.2-api/pom.xml @@ -1,214 +1,180 @@ + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to you under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + 4.0.0 + org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent + log4j-1.2-api - bundle + Apache Log4j 1.x Compatibility API The Apache Log4j 1.x Compatibility API + - ${basedir}/.. - Log4j 1.2 Documentation - /log4j12-api org.apache.log4j + + + org.apache.log4j + + + com.sun.jdmk.comm;resolution:=optional, + + javax.jms;version="[1.1,3)";resolution:=optional, + + + + org.apache.logging.log4j.core;static=true + + org.apache.logging.log4j.core + + + - junit - junit - test - - - - org.apache.felix - org.apache.felix.framework - test + javax.jms + javax.jms-api + provided + - org.eclipse.tycho - org.eclipse.osgi + org.jspecify + jspecify test + org.apache.logging.log4j log4j-api + org.apache.logging.log4j - log4j-api - test-jar - test + log4j-core + true + org.apache.logging.log4j - log4j-core + log4j-api-test + test + org.apache.logging.log4j - log4j-core - test-jar + log4j-core-test test + - org.apache.velocity - velocity - 1.7 + org.awaitility + awaitility test + commons-io commons-io test + + + org.apache.commons + commons-lang3 + test + + com.fasterxml.jackson.dataformat jackson-dataformat-xml test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + org.hamcrest + hamcrest + test + + + + org.mockito + mockito-core + test + + - - - - - org.apache.maven.plugins - maven-remote-resources-plugin - - - - process - + + + + + + + java8-incompat-fixes + + + + + !env.CI + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin - false + --add-opens java.base/java.io=ALL-UNNAMED - - - - - org.apache.felix - maven-bundle-plugin - - - org.apache.logging.log4j.core - org.apache.log4j.* - - - - - - - - - org.apache.maven.plugins - maven-changes-plugin - ${changes.plugin.version} - - - - changes-report - - - - - %URL%/show_bug.cgi?id=%ISSUE% - true - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${checkstyle.plugin.version} - - - ${log4jParentDir}/checkstyle.xml - ${log4jParentDir}/checkstyle-suppressions.xml - false - basedir=${basedir} - licensedir=${log4jParentDir}/checkstyle-header.txt - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc.plugin.version} - - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
- 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.

]]>
- - false - true -
- - - non-aggregate - - javadoc - - - -
- - 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 - - jxr - - - - aggregate - - aggregate - - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${pmd.plugin.version} - - ${maven.compiler.target} - - -
-
-
+ + + + + + + + diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Appender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Appender.java index de89ccef783..543ae088990 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Appender.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Appender.java @@ -1,18 +1,18 @@ /* * 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; @@ -69,7 +69,6 @@ public interface Appender { */ void doAppend(LoggingEvent event); - /** * Get the name of this appender. * @@ -77,7 +76,6 @@ public interface Appender { */ String getName(); - /** * Set the {@link ErrorHandler} for this appender. * @param errorHandler The error handler. @@ -110,7 +108,6 @@ public interface Appender { */ Layout getLayout(); - /** * Set the name of this appender. The name is used by other * components to identify this appender. @@ -140,4 +137,3 @@ public interface Appender { */ boolean requiresLayout(); } - 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..886389869ac 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 @@ -1,18 +1,18 @@ /* * 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; @@ -43,21 +43,16 @@ public abstract class AppenderSkeleton implements Appender, OptionHandler { /** * Create new instance. */ - public AppenderSkeleton() { - super(); - } + public AppenderSkeleton() {} - protected AppenderSkeleton(final boolean isActive) { - super(); - } + protected AppenderSkeleton(final boolean isActive) {} @Override - public void activateOptions() { - } + public void activateOptions() {} @Override - public void addFilter(Filter newFilter) { - if(headFilter == null) { + public void addFilter(final Filter newFilter) { + if (headFilter == null) { headFilter = tailFilter = newFilter; } else { tailFilter.setNext(newFilter); @@ -73,8 +68,7 @@ public void clearFilters() { } @Override - public void finalize() { - } + public void finalize() {} @Override public ErrorHandler getErrorHandler() { @@ -104,73 +98,59 @@ 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..924ce30c348 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 @@ -1,45 +1,64 @@ /* * 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 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..a81409c0246 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 @@ -1,213 +1,278 @@ /* * 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 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.helpers.OptionConverter; +import org.apache.log4j.legacy.core.CategoryUtil; +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 { - - private static PrivateAdapter adapter = new PrivateAdapter(); - - private static final Map> CONTEXT_MAP = - new WeakHashMap<>(); +public class Category implements AppenderAttachable { private static final String FQCN = Category.class.getName(); /** - * Resource bundle for localized messages. + * 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 */ - protected ResourceBundle bundle = null; - - private final org.apache.logging.log4j.core.Logger logger; + @Deprecated + public static Logger exists(final String name) { + return LogManager.exists(name, StackLocatorUtil.getCallerClassLoader(2)); + } /** - * Constructor used by Logger to specify a LoggerContext. - * @param context The LoggerContext. - * @param name The name of the Logger. + * 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. */ - protected Category(final LoggerContext context, final String name) { - this.logger = context.getLogger(name); + @SuppressWarnings("rawtypes") + @Deprecated + public static Enumeration getCurrentCategories() { + return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); } /** - * Constructor exposed by Log4j 1.2. - * @param name The name of the Logger. + * Gets the default LoggerRepository instance. + * + * @return the default LoggerRepository instance. + * @deprecated Please use {@link LogManager#getLoggerRepository()} instead. + * @since 1.0 */ - protected Category(final String name) { - this(PrivateManager.getContext(), name); + @Deprecated + public static LoggerRepository getDefaultHierarchy() { + return LogManager.getLoggerRepository(); } - private Category(final org.apache.logging.log4j.core.Logger logger) { - this.logger = logger; + 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 getInstance(PrivateManager.getContext(), name, adapter); + return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); } - static Logger getInstance(final LoggerContext context, final String name) { - return getInstance(context, name, adapter); + public static Category getRoot() { + return LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)); } - 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; + private static String getSubName(final String name) { + if (Strings.isEmpty(name)) { + return null; } - logger = factory.makeNewLoggerInstance(name); - final Logger prev = loggers.putIfAbsent(name, logger); - return prev == null ? logger : prev; + final int i = name.lastIndexOf('.'); + return i > 0 ? name.substring(0, i) : Strings.EMPTY; } - 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; + /** + * Shuts down the current configuration. + */ + public static void shutdown() { + // Depth 2 gets the call site of this method. + LogManager.shutdown(StackLocatorUtil.getCallerClassLoader(2)); } - public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) { - return getInstance(clazz.getName()); - } + /** + * The name of this category. + */ + protected String name; - static Logger getInstance(final LoggerContext context, @SuppressWarnings("rawtypes") final Class clazz) { - return getInstance(context, clazz.getName()); - } + /** + * 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; - public final String getName() { - return logger.getName(); - } + /** + * The assigned level of this category. The level variable need not be assigned a value in which case it is + * inherited form the hierarchy. + */ + protected volatile Level level; - org.apache.logging.log4j.core.Logger getLogger() { - return logger; - } + /** + * The parent of this category. All categories have at least one ancestor which is the root category. + */ + protected volatile Category parent; - public final Category getParent() { - final org.apache.logging.log4j.core.Logger parent = logger.getParent(); - if (parent == null) { - return null; - } - final ConcurrentMap loggers = getLoggersMap(logger.getContext()); - final Logger l = loggers.get(parent.getName()); - return l == null ? new Category(parent) : l; - } + /** + * Resource bundle for localized messages. + */ + protected ResourceBundle bundle; - public static Category getRoot() { - return getInstance(Strings.EMPTY); - } + private final org.apache.logging.log4j.Logger logger; - static Logger getRoot(final LoggerContext context) { - return getInstance(context, Strings.EMPTY); - } + /** Categories need to know what Hierarchy they are in. */ + protected LoggerRepository repository; - 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); - } - return map; - } - } + AppenderAttachableImpl aai; /** - Returns all the currently defined categories in the default - hierarchy as an {@link java.util.Enumeration Enumeration}. + * 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); + } -

The root category is not included in the returned - {@link Enumeration}. - @return and Enumeration of the Categories. + Category(final org.apache.logging.log4j.Logger logger) { + this.logger = logger; + } - @deprecated Please use {@link LogManager#getCurrentLoggers()} instead. + /** + * Constructor exposed by Log4j 1.2. + * + * @param name The name of the Logger. */ - @SuppressWarnings("rawtypes") - @Deprecated - public static Enumeration getCurrentCategories() { - return LogManager.getCurrentLoggers(); - } - - public final Level getEffectiveLevel() { - switch (logger.getLevel().getStandardLevel()) { - case ALL: - return Level.ALL; - case TRACE: - return Level.TRACE; - case DEBUG: - return Level.DEBUG; - case INFO: - return Level.INFO; - case WARN: - return Level.WARN; - case ERROR: - 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(); + protected Category(final String name) { + this(Hierarchy.getContext(), name); } - public final Level getLevel() { - return getEffectiveLevel(); + /** + * 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 void setLevel(final Level level) { - logger.setLevel(org.apache.logging.log4j.Level.toLevel(level.levelStr)); + /** + * 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); + } } - public final Level getPriority() { - return getEffectiveLevel(); + /** + * 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); + } } - public void setPriority(final Priority priority) { - logger.setLevel(org.apache.logging.log4j.Level.toLevel(priority.levelStr)); + /** + * 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(); + } + } + } } public void debug(final Object message) { @@ -218,10 +283,6 @@ public void debug(final Object message, final Throwable t) { maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t); } - public boolean isDebugEnabled() { - return logger.isDebugEnabled(); - } - public void error(final Object message) { maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null); } @@ -230,22 +291,6 @@ public void error(final Object message, final Throwable t) { maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t); } - public boolean isErrorEnabled() { - return logger.isErrorEnabled(); - } - - public void warn(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null); - } - - public void warn(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t); - } - - public boolean isWarnEnabled() { - return logger.isWarnEnabled(); - } - public void fatal(final Object message) { maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null); } @@ -254,123 +299,152 @@ public void fatal(final Object message, final Throwable t) { maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t); } - 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); + /** + * 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); + } + } } - public boolean isTraceEnabled() { - return logger.isTraceEnabled(); + private static Message createMessage(final 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); } - 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 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); + } } - /** - * No-op implementation. - * @param appender The Appender to add. - */ - public void addAppender(final Appender appender) { + public boolean getAdditivity() { + return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false; } /** - * No-op implementation. - * @param event The logging event. + * 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. */ - public void callAppenders(final LoggingEvent event) { - } - - @SuppressWarnings("rawtypes") + @Override + @SuppressWarnings("unchecked") public Enumeration getAllAppenders() { - return NullEnumeration.getInstance(); + 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(); } /** - * No-op implementation. - * @param name The name of the Appender. - * @return null. + * Look for the appender named as name. + *

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

*/ + @Override public Appender getAppender(final String name) { - return null; + if (LogManager.isLog4jCorePresent()) { + return AppenderWrapper.adapt(CategoryUtil.getAppenders(logger).get(name)); + } + return aai != null ? aai.getAppender(name) : null; } - /** - 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; + public Priority getChainedPriority() { + return getEffectiveLevel(); } - /** - * No-op implementation. - */ - public void removeAllAppenders() { + public Level getEffectiveLevel() { + return OptionConverter.convertLevel(logger.getLevel()); } /** - * No-op implementation. - * @param appender The Appender to remove. + * Gets the {@link LoggerRepository} where this Category instance is attached. + * + * @deprecated Please use {@link #getLoggerRepository()} instead. + * @since 1.1 */ - public void removeAppender(final Appender appender) { + @Deprecated + public LoggerRepository getHierarchy() { + return repository; } - /** - * No-op implementation. - * @param name The Appender to remove. - */ - public void removeAppender(final String name) { + public final Level getLevel() { + final org.apache.logging.log4j.Level v2Level = CategoryUtil.getExplicitLevel(logger); + return v2Level != null ? OptionConverter.convertLevel(v2Level) : null; } - /** - * No-op implementation. - */ - public static void shutdown() { + private String getLevelStr(final Priority priority) { + return priority == null ? null : priority.levelStr; } - - 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); + org.apache.logging.log4j.Logger getLogger() { + return logger; } - public boolean exists(final String name) { - return PrivateManager.getContext().hasLogger(name); + /** + * Gets the {@link LoggerRepository} where this Category is attached. + * + * @since 1.2 + */ + public LoggerRepository getLoggerRepository() { + return repository; } - public boolean getAdditivity() { - return logger.isAdditive(); + public final String getName() { + return logger.getName(); } - public void setAdditivity(final boolean additivity) { - logger.setAdditive(additivity); + 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); + Category parentLogger = loggers.get(parent.getName()); + if (parentLogger == null) { + parentLogger = new Category(parent); + parentLogger.setHierarchy(getLoggerRepository()); + } + return parentLogger; } - public void setResourceBundle(final ResourceBundle bundle) { - this.bundle = bundle; + public final Level getPriority() { + return getLevel(); } public ResourceBundle getResourceBundle() { @@ -378,44 +452,69 @@ public ResourceBundle getResourceBundle() { return bundle; } 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; + 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 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); + } + /** - If assertion parameter is {@code false}, then - logs msg as an {@link #error(Object) error} statement. + * 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 boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } -

The assert method has been renamed to - assertLog because assert is a language - reserved word in JDK 1.4. + private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { + return logger.isEnabled(level); + } - @param assertion The assertion. - @param msg The message to print if assertion is - false. + public boolean isEnabledFor(final Priority level) { + return isEnabledFor(level.getVersion2Level()); + } - @since 1.2 - */ - public void assertLog(final boolean assertion, final String msg) { - if (!assertion) { - this.error(msg); - } + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); } - 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); - } + public boolean isFatalEnabled() { + return logger.isFatalEnabled(); + } + + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); } public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) { @@ -425,64 +524,139 @@ public void l7dlog(final Priority priority, final String key, final Object[] par } } - public void log(final Priority priority, final Object message, final Throwable t) { + public void l7dlog(final Priority priority, final String key, final Throwable t) { if (isEnabledFor(priority)) { - final Message msg = new ObjectMessage(message); + final Message msg = new LocalizedMessage(bundle, key, null); forcedLog(FQCN, priority, msg, t); } } public void log(final Priority priority, final Object message) { if (isEnabledFor(priority)) { - final Message msg = new ObjectMessage(message); - forcedLog(FQCN, priority, msg, null); + forcedLog(FQCN, priority, message, null); } } - public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) { + 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); + forcedLog(FQCN, priority, message, t); } } - 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 log(final String fqcn, final Priority priority, final Object message, final Throwable t) { + if (isEnabledFor(priority)) { + forcedLog(fqcn, priority, message, t); } } - private static class PrivateAdapter extends AbstractLoggerAdapter { + 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); + } + } + } - @Override - protected Logger newLogger(final String name, final org.apache.logging.log4j.spi.LoggerContext context) { - return new Logger((LoggerContext) context, name); + /** + * 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; } + } - @Override - protected org.apache.logging.log4j.spi.LoggerContext getContext() { - return PrivateManager.getContext(); + /** + * 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); } } /** - * Private LogManager. + * Removes the appender with the name passed as parameter form the list of appenders. + * + * @param name The Appender to remove. + * @since 0.8.2 */ - private static class PrivateManager extends org.apache.logging.log4j.LogManager { - private static final String FQCN = Category.class.getName(); + @Override + public void removeAppender(final String name) { + if (name == null || aai == null) { + return; + } + final Appender appender = aai.getAppender(name); + aai.removeAppender(name); + if (appender != null) { + fireRemoveAppenderEvent(appender); + } + } - public static LoggerContext getContext() { - return (LoggerContext) getContext(FQCN, false); + public void setAdditivity(final boolean additivity) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setAdditivity(logger, additivity); } + } - public static org.apache.logging.log4j.Logger getLogger(final String name) { - return getLogger(FQCN, name); + /** + * 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 setLevel(final Level level) { + setLevel(level != null ? level.getVersion2Level() : null); + } + + private void setLevel(final org.apache.logging.log4j.Level level) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setLevel(logger, level); } } - private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { - return logger.isEnabled(level, null, null); + public void setPriority(final Priority priority) { + setLevel(priority != null ? priority.getVersion2Level() : null); + } + + public void setResourceBundle(final ResourceBundle bundle) { + this.bundle = bundle; } + public void warn(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, 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..209ccf7be28 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/CategoryKey.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; + +/** + * 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 + public final int hashCode() { + return hashCache; + } + + @Override + public final 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..59bfedff040 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/ConsoleAppender.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.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(final 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..ae57a544ae0 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultCategoryFactory.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 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..79129edbb34 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultThrowableRenderer.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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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. + */ + @SuppressFBWarnings( + value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", + justification = "The throwable is formatted into a log file, which should be private.") + 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..25b6edf77d2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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(final Layout layout, final 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(final Layout layout, final String filename, final 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( + final Layout layout, + final String filename, + final boolean append, + final boolean bufferedIO, + final 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(final 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(final boolean bufferedIO) { + this.bufferedIO = bufferedIO; + if (bufferedIO) { + immediateFlush = false; + } + } + + /** + * Set the size of the IO buffer. + */ + public void setBufferSize(final 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(final String file) { + // Trim spaces from both ends. The users probably does not want + // trailing spaces in file names. + final 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. + */ + @SuppressFBWarnings( + value = {"PATH_TRAVERSAL_IN", "PATH_TRAVERSAL_OUT"}, + justification = "The file name comes from a configuration file.") + 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 + // + final String parentName = new File(fileName).getParent(); + if (parentName != null) { + final 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(final 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..850ea52bd61 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java @@ -0,0 +1,537 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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; + +// WARNING This class MUST not have references to the Category or +// WARNING RootCategory classes in its static initialization neither +// WARNING directly nor indirectly. +/** + * 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(); + + @SuppressFBWarnings( + value = "HSM_HIDING_METHOD", + justification = "The class is private, no confusion can arise.") + public static LoggerContext getContext() { + return getContext(FQCN, false); + } + + @SuppressFBWarnings( + value = "HSM_HIDING_METHOD", + justification = "The class is private, no confusion can arise.") + 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(); + } + + private Logger getInstance(final LoggerContext context, final String name) { + return getInstance(context, name, LOGGER_ADAPTER); + } + + private Logger getInstance(final LoggerContext context, final String name, final LoggerFactory factory) { + return getLoggersMap(context).computeIfAbsent(name, k -> { + final Logger logger = factory.makeNewLoggerInstance(name); + logger.setHierarchy(this); + return logger; + }); + } + + private Logger getInstance(final LoggerContext context, final String name, final PrivateLoggerAdapter factory) { + return getLoggersMap(context).computeIfAbsent(name, k -> { + final Logger logger = factory.newLogger(name, context); + logger.setHierarchy(this); + return logger; + }); + } + + static ConcurrentMap getLoggersMap(final LoggerContext context) { + synchronized (CONTEXT_MAP) { + return CONTEXT_MAP.computeIfAbsent(context, k -> new ConcurrentHashMap<>()); + } + } + + 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); + } + } +} 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..ae0866518e1 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 @@ -1,18 +1,18 @@ /* * 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; @@ -24,6 +24,8 @@ */ public abstract class Layout { + public static final 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(); @@ -61,7 +63,6 @@ public String getFooter() { return null; } - /** * If the layout handles the throwable object contained within * {@link LoggingEvent}, then the layout should return @@ -84,4 +85,3 @@ public String getFooter() { */ public abstract boolean ignoresThrowable(); } - 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..7bc068a3faf 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 @@ -1,28 +1,29 @@ /* * 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.apache.logging.log4j.util.Strings.toRootUpperCase; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; 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,23 @@ 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, version2Equivalent); + } /** * Convert the string passed as argument to a level. If the @@ -175,26 +183,26 @@ public static Level toLevel(final String sArg, final Level defaultLevel) { if (sArg == null) { return defaultLevel; } - final String s = sArg.toUpperCase(Locale.ROOT); + final String s = toRootUpperCase(sArg); switch (s) { - case "ALL": - return Level.ALL; - case "DEBUG": - return Level.DEBUG; - case "INFO": - return Level.INFO; - case "WARN": - return Level.WARN; - case "ERROR": - return Level.ERROR; - case "FATAL": - return Level.FATAL; - case "OFF": - return Level.OFF; - case "TRACE": - return Level.TRACE; - default: - return defaultLevel; + case "ALL": + return Level.ALL; + case "DEBUG": + return Level.DEBUG; + case "INFO": + return Level.INFO; + case "WARN": + return Level.WARN; + case "ERROR": + return Level.ERROR; + case "FATAL": + return Level.FATAL; + case "OFF": + return Level.OFF; + case "TRACE": + return Level.TRACE; + default: + return defaultLevel; } } @@ -213,6 +221,7 @@ private void readObject(final ObjectInputStream s) throws IOException, ClassNotF if (levelStr == null) { levelStr = Strings.EMPTY; } + version2Level = OptionConverter.createLevel(this); } /** @@ -247,6 +256,4 @@ protected Object readResolve() throws ObjectStreamException { // return this; } - } - 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..933c97148ef 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,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. - */ -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.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"; + + private static RepositorySelector repositorySelector; + + private static final boolean LOG4J_CORE_PRESENT; + + static { + LOG4J_CORE_PRESENT = checkLog4jCore(); + // By default, we use a DefaultRepositorySelector which always returns 'hierarchy'. + final Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG)); + repositorySelector = new DefaultRepositorySelector(hierarchy); + } + + private static boolean checkLog4jCore() { + try { + return Class.forName("org.apache.logging.log4j.core.LoggerContext") != null; + } catch (final Throwable ex) { + return false; + } + } + + /** + * 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..aa15d237cfb 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 @@ -1,66 +1,71 @@ /* * 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 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(@SuppressWarnings("rawtypes") 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 boolean isTraceEnabled() { + return getLogger().isTraceEnabled(); + } - public static LoggerContext getContext() { - return (LoggerContext) getContext(FQCN, false); - } + 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..83d2df57628 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 @@ -1,25 +1,24 @@ /* * 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 java.util.HashMap; import java.util.Hashtable; import java.util.Map; - import org.apache.logging.log4j.ThreadContext; /** @@ -28,30 +27,25 @@ */ public final class MDC { + private static final ThreadLocal> localMap = new InheritableThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap<>(); + } - private static ThreadLocal> localMap = - new InheritableThreadLocal>() { - @Override - protected Map initialValue() { - return new HashMap<>(); - } - - @Override - protected Map childValue(final Map parentValue) { - return parentValue == null ? new HashMap() : new HashMap<>(parentValue); - } - }; - - private MDC() { - } + @Override + protected Map childValue(final Map parentValue) { + return parentValue == null ? new HashMap<>() : new HashMap<>(parentValue); + } + }; + private MDC() {} public static void put(final String key, final String value) { localMap.get().put(key, value); ThreadContext.put(key, value); } - public static void put(final String key, final Object value) { localMap.get().put(key, value); ThreadContext.put(key, value.toString()); 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..4631fc3f373 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 @@ -1,30 +1,29 @@ /* * 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 java.util.Stack; /** - * + * This class does not use generics to provide better source compatibility. */ public final class NDC { - private NDC() { - } + private NDC() {} /** * Clear any nested diagnostic information if any. This method is @@ -39,7 +38,6 @@ public static void clear() { org.apache.logging.log4j.ThreadContext.clearStack(); } - /** * Clone the diagnostic context for the current thread. *

@@ -52,20 +50,20 @@ 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() { final Stack stack = new Stack<>(); - for (final String element : org.apache.logging.log4j.ThreadContext.cloneStack().asList()) { + for (final String element : + org.apache.logging.log4j.ThreadContext.cloneStack().asList()) { stack.push(element); } return stack; } - /** - * 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,13 +81,13 @@ 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); } - /** * Never use this method directly. * 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..7331cfeda9d 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 @@ -1,40 +1,132 @@ /* * 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 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 static final String DEFAULT_CONVERSION_PATTERN = "%m%n"; + + /** + * A conversion pattern equivalent to the TTCCCLayout. Current value is {@value #TTCC_CONVERSION_PATTERN} + */ + public static final 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..5b72275bd11 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 @@ -1,21 +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. */ package org.apache.log4j; +import org.apache.log4j.helpers.OptionConverter; + /** * Refrain from using this class directly, use * the {@link Level} class instead. @@ -53,7 +55,7 @@ public class Priority { * application. */ public static final int DEBUG_INT = 10000; - //public final static int FINE_INT = DEBUG_INT; + // public final static int FINE_INT = DEBUG_INT; /** * The ALL has the lowest possible rank and is intended to * turn on all logging. @@ -64,31 +66,31 @@ public class Priority { * @deprecated Use {@link Level#FATAL} instead. */ @Deprecated - public static final Priority FATAL = new Level(FATAL_INT, "FATAL", 0); + public static final Priority FATAL = new Priority(FATAL_INT, "FATAL", 0, org.apache.logging.log4j.Level.FATAL); /** * @deprecated Use {@link Level#ERROR} instead. */ @Deprecated - public static final Priority ERROR = new Level(ERROR_INT, "ERROR", 3); + public static final Priority ERROR = new Priority(ERROR_INT, "ERROR", 3, org.apache.logging.log4j.Level.ERROR); /** * @deprecated Use {@link Level#WARN} instead. */ @Deprecated - public static final Priority WARN = new Level(WARN_INT, "WARN", 4); + public static final Priority WARN = new Priority(WARN_INT, "WARN", 4, org.apache.logging.log4j.Level.WARN); /** * @deprecated Use {@link Level#INFO} instead. */ @Deprecated - public static final Priority INFO = new Level(INFO_INT, "INFO", 6); + public static final Priority INFO = new Priority(INFO_INT, "INFO", 6, org.apache.logging.log4j.Level.INFO); /** * @deprecated Use {@link Level#DEBUG} instead. */ @Deprecated - public static final Priority DEBUG = new Level(DEBUG_INT, "DEBUG", 7); + public static final Priority DEBUG = new Priority(DEBUG_INT, "DEBUG", 7, org.apache.logging.log4j.Level.DEBUG); /* * These variables should be private but were not in Log4j 1.2 so are left the same way here. @@ -96,14 +98,13 @@ public class Priority { transient int level; transient String levelStr; transient int syslogEquivalent; + transient org.apache.logging.log4j.Level version2Level; /** * Default constructor for deserialization. */ protected Priority() { - level = DEBUG_INT; - levelStr = "DEBUG"; - syslogEquivalent = 7; + this(DEBUG_INT, "DEBUG", 7, org.apache.logging.log4j.Level.DEBUG); } /** @@ -113,9 +114,18 @@ protected Priority() { * @param syslogEquivalent The equivalent syslog value. */ protected Priority(final int level, final String levelStr, final int syslogEquivalent) { + this(level, levelStr, syslogEquivalent, null); + } + + Priority( + final int level, + final String levelStr, + final int syslogEquivalent, + final org.apache.logging.log4j.Level version2Equivalent) { this.level = level; this.levelStr = levelStr; this.syslogEquivalent = syslogEquivalent; + this.version2Level = version2Equivalent != null ? version2Equivalent : OptionConverter.createLevel(this); } /** @@ -143,11 +153,18 @@ public int hashCode() { * Returns the syslog equivalent of this priority as an integer. * @return The equivalent syslog value. */ - public - final int getSyslogEquivalent() { + public 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 @@ -173,11 +190,9 @@ public boolean isGreaterOrEqual(final Priority r) { */ @Deprecated public static Priority[] getAllPossiblePriorities() { - return new Priority[]{Priority.FATAL, Priority.ERROR, Level.WARN, - Priority.INFO, Priority.DEBUG}; + return new Priority[] {Priority.FATAL, Priority.ERROR, Level.WARN, Priority.INFO, Priority.DEBUG}; } - /** * Returns the string representation of this priority. * @return The name of the Priority. @@ -223,7 +238,8 @@ public static Priority toPriority(final int val) { */ @Deprecated public static Priority toPriority(final int val, final Priority defaultPriority) { - return Level.toLevel(val, (Level) defaultPriority); + Level result = Level.toLevel(val, null); + return result == null ? defaultPriority : result; } /** @@ -234,6 +250,7 @@ public static Priority toPriority(final int val, final Priority defaultPriority) */ @Deprecated public static Priority toPriority(final String sArg, final Priority defaultPriority) { - return Level.toLevel(sArg, (Level) defaultPriority); + Level result = Level.toLevel(sArg, null); + return result == null ? defaultPriority : result; } } 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..647b86e5b92 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 @@ -1,126 +1,674 @@ /* * 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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.ConfigurationFactory; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.apache.logging.log4j.util.PropertiesUtil; +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(); + } + } + + private static final String CATEGORY_PREFIX = "log4j.category."; + private static final String LOGGER_PREFIX = "log4j.logger."; + private static final String FACTORY_PREFIX = "log4j.factory"; + private static final String ADDITIVITY_PREFIX = "log4j.additivity."; + private static final String APPENDER_PREFIX = "log4j.appender."; + private static final String RENDERER_PREFIX = "log4j.renderer."; + + 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"; + + private static final String INTERNAL_ROOT_NAME = "root"; + + private static boolean isFullCompatibilityEnabled() { + return PropertiesUtil.getProperties().getBooleanProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL); + } + private static void warnFullCompatibilityDisabled() { + LogLog.warn( + "Ignoring `PropertyConfigurator` call, since `log4j1.compatibility` is not enabled.\n" + + "See https://logging.staged.apache.org/log4j/2.x/migrate-from-log4j1.html#log4j1.compatibility for details."); } /** - * Read configuration options from properties. + * 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)); + } + + /** + * 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)); + } + + private static void configureAndWatch( + final String configFilename, final long delay, final ClassLoader classLoader) { + if (isFullCompatibilityEnabled()) { + final PropertyWatchdog watchdog = new PropertyWatchdog(configFilename, classLoader); + watchdog.setDelay(delay); + watchdog.start(); + } else { + warnFullCompatibilityDisabled(); + } + } + + /** + * 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) { + if (isFullCompatibilityEnabled()) { + 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 + "."); + } + } else { + warnFullCompatibilityDisabled(); + } } /** * 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)); + } + + private void doConfigure( + final InputStream inputStream, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + 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. + *

+ * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * - * @param configFilename A file in key=value format. + * @param properties The properties + * @param loggerRepository The hierarchy */ - public static void configureAndWatch(final String configFilename) { + private void doConfigure( + final Properties properties, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + if (isFullCompatibilityEnabled()) { + final PropertiesConfiguration configuration = + new PropertiesConfiguration(LogManager.getContext(classLoader), properties); + configuration.doConfigure(); + + repository = loggerRepository; + + // We don't want to hold references to appenders preventing their + // garbage collection. + registry.clear(); + + org.apache.logging.log4j.core.config.Configurator.reconfigure(configuration); + } else { + warnFullCompatibilityDisabled(); + } } /** - * 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 + */ + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The filename comes from a system property.") + private void doConfigure( + final String fileName, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + if (isFullCompatibilityEnabled()) { + try (final InputStream inputStream = Files.newInputStream(Paths.get(fileName))) { + doConfigure(inputStream, loggerRepository, classLoader); + } catch (final Exception e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("Could not read configuration file [" + fileName + "].", e); + LogLog.error("Ignoring configuration file [" + fileName + "]."); + } + } else { + warnFullCompatibilityDisabled(); + } + } + + /** + * 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)); + } + + private void doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + if (isFullCompatibilityEnabled()) { + LogLog.debug("Reading configuration from URL " + url); + try { + final URLConnection urlConnection = UrlConnectionFactory.createConnection(url); + try (final InputStream inputStream = urlConnection.getInputStream()) { + 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 + "]."); + } + } else { + warnFullCompatibilityDisabled(); + } + } + + 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. + */ + private 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.isEmpty()) { + final boolean additivity = OptionConverter.toBoolean(value, true); + LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity); + logger.setAdditivity(additivity); + } + } + + private Appender parseAppender(final Properties properties, final String appenderName) { + Appender appender = (Appender) registry.get(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); + registry.put(appender.getName(), appender); + return appender; + } + + private 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. + */ + private 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.isEmpty())) { + + // 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) { + if (!isFullCompatibilityEnabled()) { + warnFullCompatibilityDisabled(); + return; + } + 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); + } + } + } } } 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..0ac82732e6e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/ProvisionNode.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.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..5e6fbcf9d44 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RenderedMessage.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; + +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(final ObjectRenderer renderer, final 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..ce03ec2bc8a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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(final Layout layout, final String filename, final 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(final Layout layout, final 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. + *

+ */ + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The filename comes from a system property.") + public // synchronization not necessary since doAppend is alreasy synched + void rollOver() { + File target; + File file; + + if (qw != null) { + final 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); + } + } + } + + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The file name comes from a configuration file.") + public synchronized void setFile( + final String fileName, final boolean append, final boolean bufferedIO, final int bufferSize) + throws IOException { + super.setFile(fileName, append, this.bufferedIO, this.bufferSize); + if (append) { + final 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(final 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(final String value) { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(final Writer writer) { + this.qw = new CountingQuietWriter(writer, errorHandler); + } + + /** + * This method differentiates RollingFileAppender from its super class. + * + * @since 0.9.0 + */ + protected void subAppend(final LoggingEvent event) { + super.subAppend(event); + if (fileName != null && qw != null) { + final 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..1fd0ee76f26 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.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; + +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..678705a6f74 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/VectorAppender.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; + +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..23f731077bc --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java @@ -0,0 +1,381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.UnsupportedEncodingException; +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(final Layout layout, final 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(final Layout layout, final 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(final 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(final 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(final OutputStream os) { + OutputStreamWriter retval = null; + + final String enc = getEncoding(); + if (enc != null) { + try { + retval = new OutputStreamWriter(os, enc); + } catch (final UnsupportedEncodingException e) { + LOGGER.warn("Error initializing output writer: encoding {} is not supported.", enc, e); + } + } + if (retval == null) { + retval = new OutputStreamWriter(os); + } + return retval; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(final String value) { + encoding = value; + } + + /** + * Set the {@link ErrorHandler} for this WriterAppender and also the + * underlying {@link QuietWriter} if any. + */ + @Override + public synchronized void setErrorHandler(final 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(final 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(final LoggingEvent event) { + this.qw.write(this.layout.format(event)); + + if (layout.ignoresThrowable()) { + final String[] s = event.getThrowableStrRep(); + if (s != null) { + final 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) { + final 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) { + final 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..0e3bdd1c232 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.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.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 final 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(final 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(final 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(final 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..36a96be1315 --- /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(final org.apache.logging.log4j.core.Appender appender) { + if (appender instanceof Appender) { + return (Appender) appender; + } + if (appender instanceof Adapter) { + final 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(final 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(final 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(final LoggingEvent event) { + if (event instanceof LogEventAdapter) { + appender.append(((LogEventAdapter) event).getEvent()); + } + } + + @Override + public String getName() { + return appender.getName(); + } + + @Override + public void setErrorHandler(final ErrorHandler errorHandler) { + appender.setHandler(new ErrorHandlerAdapter(errorHandler)); + } + + @Override + public ErrorHandler getErrorHandler() { + return ((ErrorHandlerAdapter) appender.getHandler()).getHandler(); + } + + @Override + public void setLayout(final Layout layout) { + // Log4j 2 doesn't support this. + } + + @Override + public Layout getLayout() { + return new LayoutWrapper(appender.getLayout()); + } + + @Override + public void setName(final 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..72354d899b7 --- /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(final ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + public ErrorHandler getHandler() { + return errorHandler; + } + + @Override + public void error(final String msg) { + errorHandler.error(msg); + } + + @Override + public void error(final String msg, final Throwable t) { + if (t instanceof Exception) { + errorHandler.error(msg, (Exception) t, 0); + } else { + errorHandler.error(msg); + } + } + + @Override + public void error(final String msg, final LogEvent event, final 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..06204842eaa --- /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 final 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(final 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(final Filter first, final 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(adapt(first)); + } + return FilterWrapper.adapt(composite.addFilter(adapt(second))); + } + + private FilterAdapter(final Filter filter) { + this.filter = filter; + } + + @Override + public Result filter(final LogEvent event) { + final 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..aadb2dbdb3e --- /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(final 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(final 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(final 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..0321225cd25 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.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.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 final 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(final Layout layout) { + if (layout instanceof LayoutWrapper) { + return ((LayoutWrapper) layout).getLayout(); + } + if (layout != null) { + return new LayoutAdapter(layout); + } + return null; + } + + private LayoutAdapter(final 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(final LogEvent event) { + final String result = layout.format(new LogEventAdapter(event)); + return result == null ? null : result.getBytes(); + } + + @Override + public String toSerializable(final 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(final LogEvent event, final 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..6ac6ff65208 --- /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(final 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(final org.apache.logging.log4j.core.Layout layout) { + this.layout = layout; + } + + @Override + public String format(final 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..d3684731326 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.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.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 { + + public static final long JVM_START_TIME = initStartTime(); + + private final LogEvent event; + + public LogEventAdapter(final 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 getJvmStartTime() { + 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(final 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..3c5448b210d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.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.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(final 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 Map getContextMap() { + return contextData; + } + + @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() { + final MutableInstant mutable = new MutableInstant(); + mutable.initFromEpochMilli(event.getTimeStamp(), 0); + return mutable; + } + + @Override + public StackTraceElement getSource() { + final 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() { + final Thread thread = getThread(); + return thread != null ? thread.getId() : 0; + } + + @Override + public int getThreadPriority() { + final 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() { + final 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(final boolean endOfBatch) {} + + @Override + public void setIncludeLocation(final boolean locationRequired) {} + + @Override + public long getNanoTime() { + return 0; + } + + private static class ContextDataMap extends HashMap implements ReadOnlyStringMap { + + ContextDataMap(final Map map) { + if (map != null) { + super.putAll(map); + } + } + + @Override + public Map toMap() { + return this; + } + + @Override + public boolean containsKey(final String key) { + return super.containsKey(key); + } + + @Override + public void forEach(final BiConsumer action) { + super.forEach((k, v) -> action.accept(k, (V) v)); + } + + @Override + public void forEach(final TriConsumer action, final S state) { + super.forEach((k, v) -> action.accept(k, (V) v, state)); + } + + @Override + public V getValue(final String key) { + return (V) super.get(key); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ReadOnlyStringMap) { + // Convert to maps and compare + final Map thisMap = toMap(); + final Map otherMap = ((ReadOnlyStringMap) obj).toMap(); + return thisMap.equals(otherMap); + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return toMap().hashCode(); + } + } +} 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..8e82ece2c26 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.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.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(final RewritePolicy policy) { + this.policy = policy; + } + + @Override + public LogEvent rewrite(final LogEvent source) { + final 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..64f4dfe4e9c --- /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(final org.apache.logging.log4j.core.appender.rewrite.RewritePolicy policy) { + this.policy = policy; + } + + @Override + public LoggingEvent rewrite(final LoggingEvent source) { + final 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..dfd4319b409 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.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.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.AtomicLong; +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; + } + + public long getLongProperty(final String key, final long defaultValue) { + String value = null; + try { + value = getProperty(key); + if (value != null) { + return Long.parseLong(value); + } + } catch (final Exception ex) { + LOGGER.warn("Error converting value {} of {} to a long: {}", 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, final 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, final 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, final AtomicLong ref) { + final String value = getValueAttribute(element); + if (value == null) { + LOGGER.warn("No value for {} parameter, using default {}", name, ref); + } else { + try { + ref.set(Long.parseLong(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, final 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/BooleanHolder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BooleanHolder.java new file mode 100644 index 00000000000..564800d8b76 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BooleanHolder.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; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Holds Boolean values created inside of a Lambda expression. + * + * @deprecated Use {@link AtomicReference}. + */ +@Deprecated +public class BooleanHolder extends Holder { + + public BooleanHolder() { + super(Boolean.FALSE); + } + + @Override + public void set(final Boolean value) { + if (value != null) { + super.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..a829b7afbc9 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Builder.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.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..e799531a361 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.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.log4j.builders; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; + +import java.util.Map; +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.builders.rolling.TriggeringPolicyBuilder; +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.core.appender.rolling.TriggeringPolicy; +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.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; +import org.w3c.dom.Element; + +/** + * + */ +public class BuilderManager { + + /** Plugin category. */ + public static final String CATEGORY = "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 Map> plugins; + + /** + * Constructs a new instance. + */ + public BuilderManager() { + final PluginManager manager = new PluginManager(CATEGORY); + manager.collectPlugins(); + plugins = manager.getPlugins(); + } + + 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 = LoaderUtil.newInstanceOf(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; + } + } + + @SuppressWarnings("unchecked") + private PluginType getPlugin(final String className) { + Objects.requireNonNull(plugins, "plugins"); + Objects.requireNonNull(className, "className"); + final String key = toRootLowerCase(className).trim(); + final PluginType pluginType = plugins.get(key); + if (pluginType == null) { + LOGGER.warn("Unable to load plugin class name {} with key {}", className, key); + } + return (PluginType) pluginType; + } + + private , U> U newInstance( + final PluginType plugin, final Function consumer, final U invalidValue) { + if (plugin != null) { + try { + final T builder = LoaderUtil.newInstanceOf(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; + } + } catch (final ReflectiveOperationException ex) { + LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage()); + } + } + return null; + } + + public

, T> T parse( + final String className, + final String prefix, + final Properties props, + final PropertiesConfiguration config, + final 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); + } + + public TriggeringPolicy parseTriggeringPolicy( + final String className, final Element policyElement, final XmlConfiguration config) { + return newInstance( + this.getPlugin(className), + b -> b.parse(policyElement, config), + (TriggeringPolicy) null); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Holder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Holder.java new file mode 100644 index 00000000000..d116af58602 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Holder.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.builders; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Provides a place to hold values generated inside of a Lambda expression. + * + * @param The type of object referred to by this reference. + * @deprecated Use {@link AtomicReference}. + */ +@Deprecated +public class Holder { + private V value; + + public Holder() {} + + public Holder(final V defaultValue) { + this.value = defaultValue; + } + + public void set(final V value) { + this.value = value; + } + + public V get() { + return value; + } +} 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..c4b2ee590ff --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Parser.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; + +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..c19e2c12ba2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.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.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..000a6a1f61d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.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 static org.apache.log4j.builders.BuilderManager.CATEGORY; +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.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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 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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +/** + * Build an Async Appender + */ +@Plugin(name = "org.apache.log4j.AsyncAppender", category = CATEGORY) +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); + } + final 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..19a98ecb5a5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.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.builders.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a Console Appender + */ +@Plugin(name = "org.apache.log4j.ConsoleAppender", category = CATEGORY) +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) { + final 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..4920aaa80ae --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.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.log4j.builders.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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.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.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a Daily Rolling File Appender + */ +@Plugin(name = "org.apache.log4j.DailyRollingFileAppender", category = CATEGORY) +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); + } + + @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); + } + + 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 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().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/EnhancedRollingFileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/EnhancedRollingFileAppenderBuilder.java new file mode 100644 index 00000000000..26377e7d1be --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/EnhancedRollingFileAppenderBuilder.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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.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.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.DefaultRolloverStrategy; +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.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a File Appender + */ +@Plugin(name = "org.apache.log4j.rolling.RollingFileAppender", category = CATEGORY) +public class EnhancedRollingFileAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String TIME_BASED_ROLLING_POLICY = "org.apache.log4j.rolling.TimeBasedRollingPolicy"; + private static final String FIXED_WINDOW_ROLLING_POLICY = "org.apache.log4j.rolling.FixedWindowRollingPolicy"; + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String TRIGGERING_TAG = "triggeringPolicy"; + private static final String ROLLING_TAG = "rollingPolicy"; + private static final int DEFAULT_MIN_INDEX = 1; + private static final int DEFAULT_MAX_INDEX = 7; + private static final String ACTIVE_FILE_PARAM = "ActiveFileName"; + private static final String FILE_PATTERN_PARAM = "FileNamePattern"; + private static final String MIN_INDEX_PARAM = "MinIndex"; + private static final String MAX_INDEX_PARAM = "MaxIndex"; + + public EnhancedRollingFileAppenderBuilder() {} + + public EnhancedRollingFileAppenderBuilder(final String prefix, final Properties properties) { + super(prefix, properties); + } + + private void parseRollingPolicy( + final Element element, + final XmlConfiguration configuration, + final AtomicReference rollingPolicyClassName, + final AtomicReference activeFileName, + final AtomicReference fileNamePattern, + final AtomicInteger minIndex, + final AtomicInteger maxIndex) { + rollingPolicyClassName.set(configuration.subst(element.getAttribute("class"), getProperties())); + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case ACTIVE_FILE_PARAM: + set(ACTIVE_FILE_PARAM, currentElement, activeFileName); + break; + case FILE_PATTERN_PARAM: + set(FILE_PATTERN_PARAM, currentElement, fileNamePattern); + break; + case MIN_INDEX_PARAM: + set(MIN_INDEX_PARAM, currentElement, minIndex); + break; + case MAX_INDEX_PARAM: + set(MAX_INDEX_PARAM, currentElement, maxIndex); + } + } + }); + } + + @Override + public Appender parseAppender(final Element element, final XmlConfiguration configuration) { + // FileAppender + final String name = getNameAttribute(element); + 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); + // specific to RollingFileAppender + final AtomicReference rollingPolicyClassName = new AtomicReference<>(); + final AtomicReference activeFileName = new AtomicReference<>(); + final AtomicReference fileNamePattern = new AtomicReference<>(); + final AtomicInteger minIndex = new AtomicInteger(DEFAULT_MIN_INDEX); + final AtomicInteger maxIndex = new AtomicInteger(DEFAULT_MAX_INDEX); + final AtomicReference triggeringPolicy = new AtomicReference<>(); + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case ROLLING_TAG: + parseRollingPolicy( + currentElement, + configuration, + rollingPolicyClassName, + activeFileName, + fileNamePattern, + minIndex, + maxIndex); + break; + case TRIGGERING_TAG: + triggeringPolicy.set(configuration.parseTriggeringPolicy(currentElement)); + break; + case LAYOUT_TAG: + layout.set(configuration.parseLayout(currentElement)); + break; + case FILTER_TAG: + configuration.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, + layout.get(), + filter.get(), + fileName.get(), + level.get(), + immediateFlush.get(), + append.get(), + bufferedIo.get(), + bufferSize.get(), + rollingPolicyClassName.get(), + activeFileName.get(), + fileNamePattern.get(), + minIndex.get(), + maxIndex.get(), + triggeringPolicy.get(), + configuration); + } + + @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")); + final String rollingPolicyClassName = getProperty(ROLLING_TAG); + final int minIndex = getIntegerProperty(ROLLING_TAG + "." + MIN_INDEX_PARAM, DEFAULT_MIN_INDEX); + final int maxIndex = getIntegerProperty(ROLLING_TAG + "." + MAX_INDEX_PARAM, DEFAULT_MAX_INDEX); + final String activeFileName = getProperty(ROLLING_TAG + "." + ACTIVE_FILE_PARAM); + final String fileNamePattern = getProperty(ROLLING_TAG + "." + FILE_PATTERN_PARAM); + final TriggeringPolicy triggeringPolicy = + configuration.parseTriggeringPolicy(props, appenderPrefix + "." + TRIGGERING_TAG); + return createAppender( + name, + layout, + filter, + fileName, + level, + immediateFlush, + append, + bufferedIo, + bufferSize, + rollingPolicyClassName, + activeFileName, + fileNamePattern, + minIndex, + maxIndex, + triggeringPolicy, + configuration); + } + + private Appender createAppender( + final String name, + final Layout layout, + final Filter filter, + final String fileName, + final String level, + final boolean immediateFlush, + final boolean append, + final boolean bufferedIo, + final int bufferSize, + final String rollingPolicyClassName, + final String activeFileName, + final String fileNamePattern, + final int minIndex, + final int maxIndex, + final TriggeringPolicy triggeringPolicy, + final Configuration configuration) { + final org.apache.logging.log4j.core.Layout fileLayout = LayoutAdapter.adapt(layout); + final boolean actualImmediateFlush = bufferedIo ? false : immediateFlush; + final org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter); + if (rollingPolicyClassName == null) { + LOGGER.error("Unable to create RollingFileAppender, no rolling policy provided."); + return null; + } + final String actualFileName = activeFileName != null ? activeFileName : fileName; + if (actualFileName == null) { + LOGGER.error("Unable to create RollingFileAppender, no file name provided."); + return null; + } + if (fileNamePattern == null) { + LOGGER.error("Unable to create RollingFileAppender, no file name pattern provided."); + return null; + } + final DefaultRolloverStrategy.Builder rolloverStrategyBuilder = DefaultRolloverStrategy.newBuilder(); + switch (rollingPolicyClassName) { + case FIXED_WINDOW_ROLLING_POLICY: + rolloverStrategyBuilder.setMin(Integer.toString(minIndex)).setMax(Integer.toString(maxIndex)); + break; + case TIME_BASED_ROLLING_POLICY: + break; + default: + LOGGER.warn("Unsupported rolling policy: {}", rollingPolicyClassName); + } + final TriggeringPolicy actualTriggeringPolicy; + if (triggeringPolicy != null) { + actualTriggeringPolicy = triggeringPolicy; + } else if (rollingPolicyClassName.equals(TIME_BASED_ROLLING_POLICY)) { + actualTriggeringPolicy = TimeBasedTriggeringPolicy.newBuilder().build(); + } else { + LOGGER.error("Unable to create RollingFileAppender, no triggering policy provided."); + return null; + } + return AppenderWrapper.adapt(RollingFileAppender.newBuilder() + .setAppend(append) + .setBufferedIo(bufferedIo) + .setBufferSize(bufferedIo ? bufferSize : 0) + .setConfiguration(configuration) + .setFileName(actualFileName) + .setFilePattern(fileNamePattern) + .setFilter(fileFilter) + .setImmediateFlush(actualImmediateFlush) + .setLayout(fileLayout) + .setName(name) + .setPolicy(actualTriggeringPolicy) + .setStrategy(rolloverStrategyBuilder.build()) + .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..9d1f29a962e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.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.builders.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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.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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a File Appender + */ +@Plugin(name = "org.apache.log4j.FileAppender", category = CATEGORY) +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) { + final 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..622096c5c51 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.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.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; + +import java.util.Properties; +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.core.config.plugins.Plugin; +import org.w3c.dom.Element; + +/** + * Build a Null Appender + */ +@Plugin(name = "org.apache.log4j.varia.NullAppender", category = CATEGORY) +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..6e8718a8b3a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.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.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +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.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; +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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +/** + * Build a Rewrite Appender + */ +@Plugin(name = "org.apache.log4j.rewrite.RewriteAppender", category = CATEGORY) +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..72243829667 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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.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.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a File Appender + */ +@Plugin(name = "org.apache.log4j.RollingFileAppender", category = CATEGORY) +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()); + } + + @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); + } + + 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 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 SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy(maxSize); + final CompositeTriggeringPolicy policy = CompositeTriggeringPolicy.createPolicy(sizePolicy); + final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder() + .setConfig(config) + .setMax(maxBackups) + .setFileIndex("min") + .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..4e9ec159041 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.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.log4j.builders.appender; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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.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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a Console Appender + */ +@Plugin(name = "org.apache.log4j.net.SocketAppender", category = CATEGORY) +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) { + final 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..8d4436e03eb --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +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 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.config.plugins.Plugin; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +/** + * Build a File Appender + */ +@Plugin(name = "org.apache.log4j.net.SyslogAppender", category = CATEGORY) +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/appender/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/package-info.java new file mode 100644 index 00000000000..49a049a8390 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/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. + */ +@Open("org.apache.logging.log4j.core") +package org.apache.log4j.builders.appender; + +import aQute.bnd.annotation.jpms.Open; 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..385d4d87975 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.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.builders.filter; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; + +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.filter.DenyAllFilter; +import org.w3c.dom.Element; + +/** + * Build a Pattern Layout + */ +@Plugin(name = "org.apache.log4j.varia.DenyAllFilter", category = CATEGORY) +public class DenyAllFilterBuilder implements FilterBuilder { + + @Override + public Filter parse(final Element filterElement, final XmlConfiguration config) { + return new FilterWrapper(DenyAllFilter.newBuilder().build()); + } + + @Override + public Filter parse(final 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..9db96a971da --- /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..1ef21f4d726 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.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.builders.filter; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.filter.LevelMatchFilter; +import org.w3c.dom.Element; + +/** + * Build a Level match filter. + */ +@Plugin(name = "org.apache.log4j.varia.LevelMatchFilter", category = CATEGORY) +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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(final Element filterElement, final 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(final PropertiesConfiguration config) { + final String level = getProperty(LEVEL); + final boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(level, acceptOnMatch); + } + + private Filter createFilter(final String level, final boolean acceptOnMatch) { + Level lvl = Level.ERROR; + if (level != null) { + lvl = OptionConverter.toLevel(level, org.apache.log4j.Level.ERROR).getVersion2Level(); + } + final 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..e5bf23213ae --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.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.builders.filter; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.filter.LevelRangeFilter; +import org.w3c.dom.Element; + +/** + * Build a Level range filter. + * In this class, order of {@link Level} is log4j1 way, i.e., + * {@link Level#ALL} and {@link Level#OFF} have minimum and maximum order, respectively. + * (see: LOG4J2-2315) + */ +@Plugin(name = "org.apache.log4j.varia.LevelRangeFilter", category = CATEGORY) +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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(final Element filterElement, final 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: + levelMin.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(final PropertiesConfiguration config) { + final String levelMax = getProperty(LEVEL_MAX); + final String levelMin = getProperty(LEVEL_MIN); + final boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(levelMax, levelMin, acceptOnMatch); + } + + private Filter createFilter(final String levelMax, final String levelMin, final boolean acceptOnMatch) { + Level max = Level.OFF; + Level min = Level.ALL; + if (levelMax != null) { + max = OptionConverter.toLevel(levelMax, org.apache.log4j.Level.OFF).getVersion2Level(); + } + if (levelMin != null) { + min = OptionConverter.toLevel(levelMin, org.apache.log4j.Level.ALL).getVersion2Level(); + } + final 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; + + // XXX: LOG4J2-2315 + // log4j1 order: ALL < TRACE < DEBUG < ... < FATAL < OFF + // log4j2 order: ALL > TRACE > DEBUG > ... > FATAL > OFF + // So we create as LevelRangeFilter.createFilter(minLevel=max, maxLevel=min, ...) + return FilterWrapper.adapt( + LevelRangeFilter.createFilter(max, min, 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..c1adeed2e35 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.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.log4j.builders.filter; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.filter.StringMatchFilter; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +/** + * Build a String match filter. + */ +@Plugin(name = "org.apache.log4j.varia.StringMatchFilter", category = CATEGORY) +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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(final Element filterElement, final 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(final PropertiesConfiguration config) { + final String text = getProperty(STRING_TO_MATCH); + final boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(text, acceptOnMatch); + } + + private Filter createFilter(final String text, final boolean acceptOnMatch) { + if (text == null) { + LOGGER.error("No text provided for StringMatchFilter"); + return null; + } + final 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() + .setText(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/filter/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/package-info.java new file mode 100644 index 00000000000..1051cdee9e8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/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. + */ +@Open("org.apache.logging.log4j.core") +package org.apache.log4j.builders.filter; + +import aQute.bnd.annotation.jpms.Open; 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..5ece07d6300 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.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.builders.layout; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.w3c.dom.Element; + +/** + * Build a Pattern Layout + */ +@Plugin(name = "org.apache.log4j.HTMLLayout", category = CATEGORY) +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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(final Element layoutElement, final 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(final PropertiesConfiguration config) { + final String title = getProperty(TITLE_PARAM, DEFAULT_TITLE); + final boolean locationInfo = getBooleanProperty(LOCATION_INFO_PARAM); + return createLayout(title, locationInfo); + } + + private Layout createLayout(final String title, final 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..3eceb5d732f --- /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..9eaffb4bc0d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.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.log4j.builders.layout; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; + +import java.util.Properties; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAliases; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Build a Pattern Layout + */ +@Plugin(name = "org.apache.log4j.PatternLayout", category = CATEGORY) +@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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(final Element layoutElement, final XmlConfiguration config) { + final NodeList params = layoutElement.getElementsByTagName("param"); + final int length = params.getLength(); + String pattern = null; + for (int index = 0; index < length; ++index) { + final Node currentNode = params.item(index); + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + final 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) { + final 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..7b16e54d871 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.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.builders.layout; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; + +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.w3c.dom.Element; + +/** + * Build a Pattern Layout + */ +@Plugin(name = "org.apache.log4j.SimpleLayout", category = CATEGORY) +public class SimpleLayoutBuilder implements LayoutBuilder { + + @Override + public Layout parse(final Element layoutElement, final XmlConfiguration config) { + return new LayoutWrapper(PatternLayout.newBuilder() + .setPattern("%v1Level - %m%n") + .setConfiguration(config) + .build()); + } + + @Override + public Layout parse(final 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..ae07ff39489 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.w3c.dom.Element; + +/** + * Build a Pattern Layout + */ +@Plugin(name = "org.apache.log4j.TTCCLayout", category = CATEGORY) +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(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(final Element layoutElement, final 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(final PropertiesConfiguration config) { + final boolean threadPrinting = getBooleanProperty(THREAD_PRINTING_PARAM, true); + final boolean categoryPrefixing = getBooleanProperty(CATEGORY_PREFIXING_PARAM, true); + final boolean contextPrinting = getBooleanProperty(CONTEXT_PRINTING_PARAM, true); + final String dateFormat = getProperty(DATE_FORMAT_PARAM, RELATIVE); + final String timezone = getProperty(TIMEZONE_FORMAT); + + return createLayout(threadPrinting, categoryPrefixing, contextPrinting, dateFormat, timezone, config); + } + + private Layout createLayout( + final boolean threadPrinting, + final boolean categoryPrefixing, + final boolean contextPrinting, + final String dateFormat, + final String timezone, + final Log4j1Configuration config) { + final 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..358c2d59322 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.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.log4j.builders.layout; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +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.core.config.plugins.Plugin; +import org.w3c.dom.Element; + +/** + * Build an XML Layout + */ +@Plugin(name = "org.apache.log4j.xml.XMLLayout", category = CATEGORY) +public class XmlLayoutBuilder extends AbstractBuilder implements LayoutBuilder { + + private static final String LOCATION_INFO = "LocationInfo"; + private static final String PROPERTIES = "Properties"; + + public XmlLayoutBuilder() {} + + public XmlLayoutBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(final Element layoutElement, final 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(final PropertiesConfiguration config) { + final boolean properties = getBooleanProperty(PROPERTIES); + final boolean locationInfo = getBooleanProperty(LOCATION_INFO); + return createLayout(properties, locationInfo); + } + + private Layout createLayout(final boolean properties, final boolean locationInfo) { + return LayoutWrapper.adapt(Log4j1XmlLayout.createLayout(locationInfo, properties)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/package-info.java new file mode 100644 index 00000000000..d87f91dc2e1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/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. + */ +@Open("org.apache.logging.log4j.core") +package org.apache.log4j.builders.layout; + +import aQute.bnd.annotation.jpms.Open; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/package-info.java new file mode 100644 index 00000000000..c20555c3b34 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/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. + */ +@Export +@Open("org.apache.logging.log4j.core") +@Version("2.20.1") +package org.apache.log4j.builders; + +import aQute.bnd.annotation.jpms.Open; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..b88581f2e26 --- /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/builders/rewrite/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/package-info.java new file mode 100644 index 00000000000..f8eb75121fe --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/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. + */ +@Open("org.apache.logging.log4j.core") +package org.apache.log4j.builders.rewrite; + +import aQute.bnd.annotation.jpms.Open; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/CompositeTriggeringPolicyBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/CompositeTriggeringPolicyBuilder.java new file mode 100644 index 00000000000..2b96ee319bd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/CompositeTriggeringPolicyBuilder.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.log4j.builders.rolling; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.w3c.dom.Element; + +@Plugin(name = "org.apache.log4j.rolling.CompositeTriggeringPolicy", category = CATEGORY) +public class CompositeTriggeringPolicyBuilder extends AbstractBuilder + implements TriggeringPolicyBuilder { + + private static final TriggeringPolicy[] EMPTY_TRIGGERING_POLICIES = new TriggeringPolicy[0]; + private static final String POLICY_TAG = "triggeringPolicy"; + + public CompositeTriggeringPolicyBuilder() { + super(); + } + + public CompositeTriggeringPolicyBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public CompositeTriggeringPolicy parse(final Element element, final XmlConfiguration configuration) { + final List policies = new ArrayList<>(); + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case POLICY_TAG: + final TriggeringPolicy policy = configuration.parseTriggeringPolicy(currentElement); + if (policy != null) { + policies.add(policy); + } + break; + } + }); + return createTriggeringPolicy(policies); + } + + @Override + public CompositeTriggeringPolicy parse(final PropertiesConfiguration configuration) { + return createTriggeringPolicy(Collections.emptyList()); + } + + private CompositeTriggeringPolicy createTriggeringPolicy(final List policies) { + return CompositeTriggeringPolicy.createPolicy(policies.toArray(EMPTY_TRIGGERING_POLICIES)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/SizeBasedTriggeringPolicyBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/SizeBasedTriggeringPolicyBuilder.java new file mode 100644 index 00000000000..0d82ff23112 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/SizeBasedTriggeringPolicyBuilder.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.builders.rolling; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.w3c.dom.Element; + +@Plugin(name = "org.apache.log4j.rolling.SizeBasedTriggeringPolicy", category = CATEGORY) +public class SizeBasedTriggeringPolicyBuilder extends AbstractBuilder + implements TriggeringPolicyBuilder { + + private static final String MAX_SIZE_PARAM = "MaxFileSize"; + private static final long DEFAULT_MAX_SIZE = 10 * 1024 * 1024; + + public SizeBasedTriggeringPolicyBuilder() { + super(); + } + + public SizeBasedTriggeringPolicyBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public SizeBasedTriggeringPolicy parse(final Element element, final XmlConfiguration configuration) { + final AtomicLong maxSize = new AtomicLong(DEFAULT_MAX_SIZE); + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case MAX_SIZE_PARAM: + set(MAX_SIZE_PARAM, currentElement, maxSize); + break; + } + break; + } + }); + return createTriggeringPolicy(maxSize.get()); + } + + @Override + public SizeBasedTriggeringPolicy parse(final PropertiesConfiguration configuration) { + final long maxSize = getLongProperty(MAX_SIZE_PARAM, DEFAULT_MAX_SIZE); + return createTriggeringPolicy(maxSize); + } + + private SizeBasedTriggeringPolicy createTriggeringPolicy(final long maxSize) { + return SizeBasedTriggeringPolicy.createPolicy(Long.toString(maxSize)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TimeBasedRollingPolicyBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TimeBasedRollingPolicyBuilder.java new file mode 100644 index 00000000000..bebfd0260e3 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TimeBasedRollingPolicyBuilder.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.builders.rolling; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; + +import java.util.Properties; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +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.plugins.Plugin; +import org.w3c.dom.Element; + +@Plugin(name = "org.apache.log4j.rolling.TimeBasedRollingPolicy", category = CATEGORY) +public class TimeBasedRollingPolicyBuilder extends AbstractBuilder + implements TriggeringPolicyBuilder { + + public TimeBasedRollingPolicyBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + public TimeBasedRollingPolicyBuilder() { + super(); + } + + @Override + public TimeBasedTriggeringPolicy parse(final Element element, final XmlConfiguration configuration) { + return createTriggeringPolicy(); + } + + @Override + public TimeBasedTriggeringPolicy parse(final PropertiesConfiguration configuration) { + return createTriggeringPolicy(); + } + + private TimeBasedTriggeringPolicy createTriggeringPolicy() { + return TimeBasedTriggeringPolicy.newBuilder().build(); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TriggeringPolicyBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TriggeringPolicyBuilder.java new file mode 100644 index 00000000000..931ad08b405 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/TriggeringPolicyBuilder.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.log4j.builders.rolling; + +import org.apache.log4j.builders.Parser; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; + +public interface TriggeringPolicyBuilder extends Parser { + // NOP +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/package-info.java new file mode 100644 index 00000000000..d1ada5ae8c5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rolling/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. + */ +@Open("org.apache.logging.log4j.core") +package org.apache.log4j.builders.rolling; + +import aQute.bnd.annotation.jpms.Open; 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..8e0f0a8934a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/component/helpers/Constants.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.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/InputStreamWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java index 19bf9a9a319..49eaea9187c 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/InputStreamWrapper.java @@ -1,18 +1,18 @@ /* * 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.config; @@ -88,5 +88,4 @@ public long skip(final long n) throws IOException { public String toString() { return getClass().getSimpleName() + " [description=" + description + ", input=" + input + "]"; } - } 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..de3a3a53487 --- /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 = new BuilderManager(); + + public Log4j1Configuration( + final LoggerContext loggerContext, + final ConfigurationSource configurationSource, + final int monitorIntervalSeconds) { + super(loggerContext, configurationSource); + initializeWatchers(this, configurationSource, monitorIntervalSeconds); + } + + public BuilderManager getBuilderManager() { + return manager; + } + + /** + * Initialize the configuration. + */ + @Override + public void initialize() { + 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..c0f8e73fcb7 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 @@ -1,21 +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. */ 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; @@ -25,10 +27,13 @@ import java.nio.file.SimpleFileVisitor; 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; @@ -55,19 +60,29 @@ public final class Log4j1ConfigurationConverter { @Command(name = "Log4j1ConfigurationConverter") public static class CommandLineArguments extends BasicCommandLineArguments implements Runnable { - @Option(names = { "--failfast", "-f" }, description = "Fails on the first failure in recurse mode.") + @Option( + names = {"--failfast", "-f"}, + description = "Fails on the first failure in recurse mode.") private boolean failFast; - @Option(names = { "--in", "-i" }, description = "Specifies the input file.") + @Option( + names = {"--in", "-i"}, + description = "Specifies the input file.") private Path pathIn; - @Option(names = { "--out", "-o" }, description = "Specifies the output file.") + @Option( + names = {"--out", "-o"}, + description = "Specifies the output file.") private Path pathOut; - @Option(names = { "--recurse", "-r" }, description = "Recurses into this folder looking for the input file") + @Option( + names = {"--recurse", "-r"}, + description = "Recurses into this folder looking for the input file") private Path recurseIntoPath; - @Option(names = { "--verbose", "-v" }, description = "Be verbose.") + @Option( + names = {"--verbose", "-v"}, + description = "Be verbose.") private boolean verbose; public Path getPathIn() { @@ -145,8 +160,8 @@ private Log4j1ConfigurationConverter(final CommandLineArguments cla) { } protected void convert(final InputStream input, final OutputStream output) throws IOException { - final ConfigurationBuilder builder = new Log4j1ConfigurationParser() - .buildConfigurationBuilder(input); + final ConfigurationBuilder builder = + new Log4j1ConfigurationParser().buildConfigurationBuilder(input); builder.writeXmlConfiguration(output); } @@ -173,13 +188,20 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr verbose("Reading %s", file); String newFile = file.getFileName().toString(); final int lastIndex = newFile.lastIndexOf("."); - newFile = lastIndex < 0 ? newFile + FILE_EXT_XML + newFile = lastIndex < 0 + ? newFile + FILE_EXT_XML : newFile.substring(0, lastIndex) + FILE_EXT_XML; - final Path resolved = file.resolveSibling(newFile); - try (final InputStream input = new InputStreamWrapper(Files.newInputStream(file), file.toString()); - final OutputStream output = Files.newOutputStream(resolved)) { + final Path resolvedPath = file.resolveSibling(newFile); + try (final InputStream input = + new InputStreamWrapper(Files.newInputStream(file), file.toString()); + 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 +209,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; @@ -197,12 +225,14 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr } catch (final IOException e) { throw new ConfigurationException(e); } finally { - verbose("OK = %,d, Failures = %,d, Total = %,d", countOKs.get(), countFails.get(), - countOKs.get() + countFails.get()); + verbose( + "OK = %,d, Failures = %,d, Total = %,d", + countOKs.get(), countFails.get(), countOKs.get() + countFails.get()); } } else { verbose("Reading %s", cla.getPathIn()); - try (final InputStream input = getInputStream(); final OutputStream output = getOutputStream()) { + try (final InputStream input = getInputStream(); + final OutputStream output = getOutputStream()) { convert(input, output); } catch (final IOException e) { throw new ConfigurationException(e); @@ -216,5 +246,4 @@ private void verbose(final String template, final Object... args) { System.err.println(String.format(template, args)); } } - } 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..c75524811c6 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 @@ -1,24 +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. */ 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; @@ -54,5 +53,4 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final C protected String[] getSupportedTypes() { return SUFFIXES; } - } 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..4e8ea68650c 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 @@ -1,18 +1,18 @@ /* * 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.config; @@ -24,8 +24,9 @@ import java.util.Objects; 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 +40,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 +47,7 @@ * Experimental parser for Log4j 1.2 properties configuration files. * * This class is not thread-safe. - * + * *

* From the Log4j 1.2 Javadocs: *

@@ -66,20 +66,20 @@ 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(); + private final ConfigurationBuilder builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); /** * Parses a Log4j 1.2 properties configuration file in ISO 8859-1 encoding into a ConfigurationBuilder. * * @param input * InputStream to read from is assumed to be ISO 8859-1, and will not be closed. - * @return the populated ConfigurationBuilder, never {@literal null} + * @return the populated ConfigurationBuilder, never {@code null} * @throws IOException * if unable to read the input * @throws ConfigurationException @@ -89,8 +89,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 +100,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 +150,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()); } } } @@ -162,23 +167,23 @@ private Map buildClassToPropertyPrefixMap() { private void buildAppender(final String appenderName, final String appenderClass) { switch (appenderClass) { - case "org.apache.log4j.ConsoleAppender": - buildConsoleAppender(appenderName); - break; - case "org.apache.log4j.FileAppender": - buildFileAppender(appenderName); - break; - case "org.apache.log4j.DailyRollingFileAppender": - buildDailyRollingFileAppender(appenderName); - break; - case "org.apache.log4j.RollingFileAppender": - buildRollingFileAppender(appenderName); - break; - case "org.apache.log4j.varia.NullAppender": - buildNullAppender(appenderName); - break; - default: - reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName); + case "org.apache.log4j.ConsoleAppender": + buildConsoleAppender(appenderName); + break; + case "org.apache.log4j.FileAppender": + buildFileAppender(appenderName); + break; + case "org.apache.log4j.DailyRollingFileAppender": + buildDailyRollingFileAppender(appenderName); + break; + case "org.apache.log4j.RollingFileAppender": + buildRollingFileAppender(appenderName); + break; + case "org.apache.log4j.varia.NullAppender": + buildNullAppender(appenderName); + break; + default: + reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName); } } @@ -188,15 +193,15 @@ private void buildConsoleAppender(final String appenderName) { if (targetValue != null) { final ConsoleAppender.Target target; switch (targetValue) { - case "System.out": - target = ConsoleAppender.Target.SYSTEM_OUT; - break; - case "System.err": - target = ConsoleAppender.Target.SYSTEM_ERR; - break; - default: - reportWarning("Unknown value for console Target: " + targetValue); - target = null; + case "System.out": + target = ConsoleAppender.Target.SYSTEM_OUT; + break; + case "System.err": + target = ConsoleAppender.Target.SYSTEM_ERR; + break; + default: + reportWarning("Unknown value for console Target: " + targetValue); + target = null; } if (target != null) { appenderBuilder.addAttribute("target", target); @@ -226,52 +231,55 @@ private void buildFileAppender(final String appenderName, final AppenderComponen } private void buildDailyRollingFileAppender(final String appenderName) { - final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, - RollingFileAppender.PLUGIN_NAME); + final AppenderComponentBuilder appenderBuilder = + builder.newAppender(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)); appenderBuilder.addComponent(triggeringPolicy); - appenderBuilder - .addComponent(builder.newComponent("DefaultRolloverStrategy").addAttribute("max", Integer.MAX_VALUE)); + appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy") + .addAttribute("max", Integer.MAX_VALUE) + .addAttribute("fileIndex", "min")); builder.add(appenderBuilder); } private void buildRollingFileAppender(final String appenderName) { - final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, - RollingFileAppender.PLUGIN_NAME); + final AppenderComponentBuilder appenderBuilder = + builder.newAppender(appenderName, RollingFileAppender.PLUGIN_NAME); buildFileAppender(appenderName, appenderBuilder); final String fileName = getLog4jAppenderValue(appenderName, "File"); appenderBuilder.addAttribute("filePattern", fileName + ".%i"); final String maxFileSizeString = getLog4jAppenderValue(appenderName, "MaxFileSize", "10485760"); final String maxBackupIndexString = getLog4jAppenderValue(appenderName, "MaxBackupIndex", "1"); - final ComponentBuilder triggeringPolicy = builder.newComponent("Policies").addComponent( - builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString)); + final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") + .addComponent( + builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString)); appenderBuilder.addComponent(triggeringPolicy); - appenderBuilder.addComponent( - builder.newComponent("DefaultRolloverStrategy").addAttribute("max", maxBackupIndexString)); + appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy") + .addAttribute("max", maxBackupIndexString) + .addAttribute("fileIndex", "min")); builder.add(appenderBuilder); } - private void buildAttribute(final String componentName, final ComponentBuilder componentBuilder, - final String sourceAttributeName, final String targetAttributeName) { + private void buildAttribute( + final String componentName, + final ComponentBuilder componentBuilder, + final String sourceAttributeName, + final String targetAttributeName) { final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); if (attributeValue != null) { componentBuilder.addAttribute(targetAttributeName, attributeValue); } } - 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, - final String sourceAttributeName, final String targetAttributeName) { + private void buildMandatoryAttribute( + final String componentName, + final ComponentBuilder componentBuilder, + final String sourceAttributeName, + final String targetAttributeName) { final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); if (attributeValue != null) { componentBuilder.addAttribute(targetAttributeName, attributeValue); @@ -289,66 +297,87 @@ private void buildAppenderLayout(final String name, final AppenderComponentBuild final String layoutClass = getLog4jAppenderValue(name, "layout", null); if (layoutClass != null) { 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"); - - appenderBuilder.add(newPatternLayout(pattern)); - break; - } - case "org.apache.log4j.SimpleLayout": { - appenderBuilder.add(newPatternLayout("%level - %m%n")); - break; - } - case "org.apache.log4j.TTCCLayout": { - String pattern = "%r "; - if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) { - pattern += "[%t] "; + case "org.apache.log4j.PatternLayout": + case "org.apache.log4j.EnhancedPatternLayout": { + 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; } - pattern += "%p "; - if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) { - pattern += "%c "; + case "org.apache.log4j.SimpleLayout": { + appenderBuilder.add(newPatternLayout("%v1Level - %m%n")); + break; } - if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) { - pattern += "%notEmpty{%ndc }"; + case "org.apache.log4j.TTCCLayout": { + 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] "; + } + pattern += "%p "; + if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) { + pattern += "%c "; + } + if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) { + pattern += "%notEmpty{%ndc }"; + } + pattern += "- %m%n"; + appenderBuilder.add(newPatternLayout(pattern)); + break; } - pattern += "- %m%n"; - appenderBuilder.add(newPatternLayout(pattern)); - break; - } - case "org.apache.log4j.HTMLLayout": { - final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout"); - htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages")); - htmlLayout.addAttribute("locationInfo", - Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); - appenderBuilder.add(htmlLayout); - break; - } - case "org.apache.log4j.xml.XMLLayout": { - final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout"); - xmlLayout.addAttribute("locationInfo", - Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); - xmlLayout.addAttribute("properties", - Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE))); - appenderBuilder.add(xmlLayout); - break; - } - default: - reportWarning("Unknown layout class: " + layoutClass); + case "org.apache.log4j.HTMLLayout": { + final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout"); + htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages")); + htmlLayout.addAttribute( + "locationInfo", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); + appenderBuilder.add(htmlLayout); + break; + } + case "org.apache.log4j.xml.XMLLayout": { + final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout"); + xmlLayout.addAttribute( + "locationInfo", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); + xmlLayout.addAttribute( + "properties", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE))); + appenderBuilder.add(xmlLayout); + break; + } + default: + reportWarning("Unknown layout class: " + layoutClass); } } } @@ -386,13 +415,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 +450,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) { @@ -430,8 +459,8 @@ private String getProperty(final String key, final String defaultValue) { return value == null ? defaultValue : value; } - private String getLog4jAppenderValue(final String appenderName, final String attributeName, - final String defaultValue) { + private String getLog4jAppenderValue( + final String appenderName, final String attributeName, final String defaultValue) { return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue); } @@ -442,5 +471,4 @@ private String getLog4jValue(final String key) { private void reportWarning(final String msg) { StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg); } - } 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..3fc3d88ffec --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java @@ -0,0 +1,652 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.appender.rolling.TriggeringPolicy; +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().withStatus(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.isEmpty()) { + 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.isEmpty())) { + + // 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; + } + + public TriggeringPolicy parseTriggeringPolicy(final Properties props, final String policyPrefix) { + final String policyClass = OptionConverter.findAndSubst(policyPrefix, props); + if (policyClass == null) { + return null; + } + return manager.parse(policyClass, policyPrefix, props, this, null); + } + + 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..fb6f1383be5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfigurationFactory.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.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.core.config.plugins.Plugin; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Configures Log4j from a log4j 1 format properties file. + */ +@Plugin(name = "Log4j1PropertiesConfigurationFactory", category = ConfigurationFactory.CATEGORY) +@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(final LoggerContext loggerContext, final ConfigurationSource source) { + final 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..47dd263d814 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,151 @@ /* * 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.config; +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; +import org.apache.log4j.Appender; +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; /** + * 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) { + 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 static void setProperties(final Object obj, final Properties properties, final String prefix) { + new PropertySetter(obj).setProperties(properties, prefix); + } + + /** + * Uses JavaBeans {@link Introspector} to computer setters of object to be + * configured. + */ + protected void introspect() { + try { + final 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(final Properties properties, final String prefix) { + final 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; + } + + final 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) + final PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key)); + if (prop != null + && OptionHandler.class.isAssignableFrom(prop.getPropertyType()) + && prop.getWriteMethod() != null) { + final OptionHandler opt = (OptionHandler) + OptionConverter.instantiateByKey(properties, prefix + key, prop.getPropertyType(), null); + final 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 +160,120 @@ 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); + final 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 { + final Method setter = prop.getWriteMethod(); + if (setter == null) { + throw new PropertySetterException("No setter for property [" + name + "]."); + } + final 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(final String val, final Class type) { + if (val == null) { + return null; + } + + final 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..be1a3df0cd2 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 @@ -1,18 +1,18 @@ /* * 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.config; @@ -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/config/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/package-info.java index 7f96630203d..396cf9bbd32 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/package-info.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/package-info.java @@ -17,4 +17,11 @@ /** * Log4j 1.x compatibility layer. */ +@Export +@Version("2.20.1") +@Open("org.apache.logging.log4j.core") package org.apache.log4j.config; + +import aQute.bnd.annotation.jpms.Open; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..f8ffdb7053d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.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.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 static final String ABS_TIME_DATE_FORMAT = "ABSOLUTE"; + + /** + * String constant used to specify {@link org.apache.log4j.helpers.DateTimeDateFormat} in layouts. Current value is + * DATE. + */ + public static final 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 static final 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..6b3090559a2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.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.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..404a58a57ed --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 + */ + public synchronized 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..d806cc5628c --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.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.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..882f495ef7b --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CyclicBuffer.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.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..5412b1b4f7e --- /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 static org.apache.logging.log4j.util.Strings.toRootUpperCase; + +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. + */ +public abstract class DateLayout extends Layout { + + /** + * String constant designating no time information. Current value of this constant is NULL. + * + */ + public static final String NULL_DATE_FORMAT = "NULL"; + + /** + * String constant designating relative time. Current value of this constant is RELATIVE. + */ + public static final 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 + public static final 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 + public static final 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 = toRootUpperCase(value); + } 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..aa117c8feea --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.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.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..a43776bed76 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FileWatchdog.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.log4j.helpers; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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. + */ + public static final 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; + + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The filename comes from a system property.") + 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; + } + } + } + + protected abstract 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..1d6a9082024 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FormattingInfo.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.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..847bbb4ec0a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.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.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; + + private static long lastTime; + + private static 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..bf5bb23ec38 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/Loader.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.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."; + + private static 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). + *

    + *
+ */ + public static 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. + */ + public static 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..6c33b924987 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/LogLog.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.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. + */ + public static 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/NullEnumeration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/NullEnumeration.java index d0640044013..ff23eac8d92 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/NullEnumeration.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/NullEnumeration.java @@ -1,18 +1,18 @@ /* * 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.helpers; @@ -28,8 +28,7 @@ public final class NullEnumeration implements Enumeration { private static final NullEnumeration INSTANCE = new NullEnumeration(); - private NullEnumeration() { - } + private NullEnumeration() {} public static NullEnumeration getInstance() { return INSTANCE; 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..bc875512fa8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java @@ -0,0 +1,711 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.logging.log4j.util.Strings.toRootUpperCase; + +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(final 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(final 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 = + toRootUpperCase(level.toString()) + "#" + 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 + */ + public static 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 + */ + public static 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 = toRootUpperCase(value.trim()); + 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=[{}]:pri=[{}]", clazz, 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(toRootUpperCase(levelName)); + if (v2Level != null) { + switch (v2Level.name()) { + case "ALL": + return Level.ALL; + case "DEBUG": + return Level.DEBUG; + case "ERROR": + return Level.ERROR; + case "FATAL": + return Level.FATAL; + case "INFO": + return Level.INFO; + case "OFF": + return Level.OFF; + case "WARN": + return Level.WARN; + case "TRACE": + return Level.TRACE; + default: + return new LevelWrapper(v2Level); + } + } else { + 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(final 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..be5be195d12 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternConverter.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.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(final 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. + */ + protected abstract String convert(LoggingEvent event); + + /** + * A template method for formatting in a converter specific way. + */ + public void format(final StringBuffer sbuf, final LoggingEvent e) { + final String s = convert(e); + + if (s == null) { + if (0 < min) spacePad(sbuf, min); + return; + } + + final 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(final StringBuffer sbuf, final int length) { + int l = length; + while (l >= 32) { + sbuf.append(SPACES[5]); + l -= 32; + } + + for (int i = 4; i >= 0; i--) { + if ((l & (1 << i)) != 0) { + sbuf.append(SPACES[i]); + } + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternParser.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternParser.java new file mode 100644 index 00000000000..36c1c79dcbf --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternParser.java @@ -0,0 +1,517 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.Arrays; +import java.util.Date; +import java.util.Map; +import org.apache.log4j.Layout; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; + +// Contributors: Nelson Minar <(nelson@monkey.org> +// 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(final 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) == '{')) { + final int end = pattern.indexOf('}', i); + if (end > i) { + final 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() { + final 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; + final 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': + final String xOpt = extractOption(); + pc = new MDCPatternConverter(formattingInfo, xOpt); + currentLiteral.setLength(0); + break; + default: + LogLog.error("Unexpected char [" + c + "] at position " + i + " in conversion pattern."); + 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(final FormattingInfo formattingInfo, final int type) { + super(formattingInfo); + this.type = type; + } + + public String convert(final 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(final String value) { + literal = value; + } + + public final void format(final StringBuffer sbuf, final LoggingEvent event) { + sbuf.append(literal); + } + + public String convert(final LoggingEvent event) { + return literal; + } + } + + private static class DatePatternConverter extends PatternConverter { + private DateFormat df; + private Date date; + + DatePatternConverter(final FormattingInfo formattingInfo, final DateFormat df) { + super(formattingInfo); + date = new Date(); + this.df = df; + } + + public String convert(final 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(final FormattingInfo formattingInfo, final String key) { + super(formattingInfo); + this.key = key; + } + + public String convert(final LoggingEvent event) { + if (key == null) { + final StringBuffer buf = new StringBuffer("{"); + final Map properties = event.getProperties(); + if (properties.size() > 0) { + final 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 { + final Object val = event.getMDC(key); + if (val == null) { + return null; + } else { + return val.toString(); + } + } + } + } + + private class LocationPatternConverter extends PatternConverter { + int type; + + LocationPatternConverter(final FormattingInfo formattingInfo, final int type) { + super(formattingInfo); + this.type = type; + } + + public String convert(final LoggingEvent event) { + final 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 abstract static class NamedPatternConverter extends PatternConverter { + int precision; + + NamedPatternConverter(final FormattingInfo formattingInfo, final int precision) { + super(formattingInfo); + this.precision = precision; + } + + abstract String getFullyQualifiedName(LoggingEvent event); + + public String convert(final LoggingEvent event) { + final String n = getFullyQualifiedName(event); + if (precision <= 0) return n; + else { + final 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(final FormattingInfo formattingInfo, final int precision) { + super(formattingInfo, precision); + } + + String getFullyQualifiedName(final LoggingEvent event) { + return event.getLocationInformation().getClassName(); + } + } + + private class CategoryPatternConverter extends NamedPatternConverter { + + CategoryPatternConverter(final FormattingInfo formattingInfo, final int precision) { + super(formattingInfo, precision); + } + + String getFullyQualifiedName(final 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..f9dc6fb7871 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/QuietWriter.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.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(final Writer writer, final ErrorHandler errorHandler) { + super(writer); + setErrorHandler(errorHandler); + } + + @Override + public void write(final 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(final 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..498e014a2a4 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.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.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..f99ad0b8c7c --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java @@ -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. + */ +package org.apache.log4j.helpers; + +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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. + */ + @SuppressFBWarnings(value = "HSM_HIDING_METHOD", justification = "Legacy code") + 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. + */ + @SuppressFBWarnings(value = "HSM_HIDING_METHOD", justification = "Legacy code") + 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. + */ + @SuppressFBWarnings(value = "HSM_HIDING_METHOD", justification = "Legacy code") + public static Level toLevel(final String sArg, final Level defaultLevel) { + if (sArg == null) { + return defaultLevel; + } + + final String s = toRootUpperCase(sArg); + + 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/helpers/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/package-info.java index 00d0e1293ff..b94af3ba614 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/package-info.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/package-info.java @@ -17,4 +17,9 @@ /** * Log4j 1.x compatibility layer. */ +@Export +@Version("2.20.3") package org.apache.log4j.helpers; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..ee9c1805a3f --- /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 + */ + protected static 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..daf7add2b54 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * 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 = LoaderUtil.newInstanceOf("com.sun.jdmk.comm.HtmlAdapterServer"); + } catch (final ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + 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..2ee683240ad --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.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.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 final 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..7b3c20980fe --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.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.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 final 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..d31e63d7713 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 final 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..7be0f070f5f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 final 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..f7a4a80e6b8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.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.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..b502a304c98 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java @@ -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. + */ +/** + * This package lets you manage log4j settings using JMX. It is unfortunately not of production quality. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.jmx; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..4a3dcc11625 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.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.layout; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; + +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.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.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.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. + */ +@Plugin(name = "Log4j1SyslogLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +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 (!isValid()) { + return null; + } + 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 ? toRootLowerCase(facility.name()) : "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..a508218b119 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 @@ -1,26 +1,27 @@ /* * 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.layout; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; 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.config.Node; @@ -30,7 +31,6 @@ 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.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.Strings; @@ -42,6 +42,9 @@ @Plugin(name = "Log4j1XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 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; @@ -51,7 +54,7 @@ public static Log4j1XmlLayout createLayout( @PluginAttribute(value = "locationInfo") final boolean locationInfo, @PluginAttribute(value = "properties") final boolean properties // @formatter:on - ) { + ) { return new Log4j1XmlLayout(locationInfo, properties); } @@ -83,9 +86,10 @@ public String toSerializable(final LogEvent event) { return text.toString(); } + @SuppressFBWarnings( + value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", + justification = "The throwable is formatted into a log file, which should be private.") 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 +137,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 +146,23 @@ 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..d85c4c732ae --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.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.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.Configurator; +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)) { + Configurator.setLevel(asCore(logger), level); + } + } + + /** + * Returns the level explicitly set on the logger. + *

+ * If the Log4j API implementation does not support it, returns the effective level instead. + *

+ */ + public static Level getExplicitLevel(final Logger logger) { + return isCore(logger) ? getExplicitLevel(asCore(logger)) : logger.getLevel(); + } + + private static Level getExplicitLevel(final org.apache.logging.log4j.core.Logger logger) { + final LoggerConfig config = logger.get(); + return config.getName().equals(logger.getName()) ? config.getExplicitLevel() : null; + } + + /** + * 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..bf94a8d4093 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/ContextUtil.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.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(final 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(final 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..a677d13037a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/DefaultRenderer.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.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..e114cf51d92 --- /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..e78576193d1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererMap.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.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. + */ + public static void addRenderer( + final RendererSupport repository, final String renderedClassName, final String renderingClassName) { + StatusLogger.getLogger() + .debug("Rendering class: [" + renderingClassName + "], Rendered class: [" + renderedClassName + "]."); + final ObjectRenderer renderer = + (ObjectRenderer) OptionConverter.instantiateByClassName(renderingClassName, ObjectRenderer.class, null); + if (renderer == null) { + StatusLogger.getLogger().error("Could not instantiate renderer [" + renderingClassName + "]."); + return; + } + try { + final 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(final 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(final 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(final 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; + } + final 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(final Class clazz, final 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..3da0dbedb49 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.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.or; + +import org.apache.log4j.Layout; + +/** + */ +public class ThreadGroupRenderer implements ObjectRenderer { + + @Override + public String doRender(final Object obj) { + if (obj instanceof ThreadGroup) { + final StringBuilder sb = new StringBuilder(); + final ThreadGroup threadGroup = (ThreadGroup) obj; + sb.append("java.lang.ThreadGroup[name="); + sb.append(threadGroup.getName()); + sb.append(", maxpri="); + sb.append(threadGroup.getMaxPriority()); + sb.append("]"); + final 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..ffc5e9385af --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/MessageRenderer.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.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(final Object obj) { + if (obj instanceof Message) { + final StringBuilder sb = new StringBuilder(); + final 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/or/jms/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/package-info.java new file mode 100644 index 00000000000..85922fd2e41 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/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. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.or.jms; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/package-info.java new file mode 100644 index 00000000000..4cee049d061 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/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. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.or; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/package-info.java index 714c20030b9..7462396fa7f 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/package-info.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/package-info.java @@ -17,4 +17,9 @@ /** * Log4j 1.x compatibility layer. */ +@Export +@Version("2.20.2") package org.apache.log4j; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..a51f9e2d3cd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/FormattingInfo.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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..4b0cb5ae814 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1LevelPatternConverter.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.pattern; + +import org.apache.log4j.helpers.OptionConverter; +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; + +/** + * Outputs the Log4j 1.x level name. + */ +@Plugin(name = "Log4j1LevelPatternConverter", category = PatternConverter.CATEGORY) +@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(final LogEvent event, final 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..17fbc3568fe 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 @@ -1,18 +1,18 @@ /* * 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.pattern; @@ -29,7 +29,7 @@ * within the property bundle when this pattern converter has the option set. */ @Plugin(name = "Log4j1MdcPatternConverter", category = PatternConverter.CATEGORY) -@ConverterKeys({ "properties" }) +@ConverterKeys({"properties"}) public final class Log4j1MdcPatternConverter extends LogEventPatternConverter { /** * Name of property to output. @@ -79,10 +79,6 @@ 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) { + 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..5a37e79008f 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 @@ -1,21 +1,22 @@ /* * 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.pattern; +import java.util.List; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.pattern.ConverterKeys; @@ -23,20 +24,16 @@ import org.apache.logging.log4j.core.pattern.PatternConverter; import org.apache.logging.log4j.util.Strings; -import java.util.List; - - /** * Returns the event's NDC in a StringBuilder. */ @Plugin(name = "Log4j1NdcPatternConverter", category = PatternConverter.CATEGORY) -@ConverterKeys({ "ndc" }) +@ConverterKeys({"ndc"}) public final class Log4j1NdcPatternConverter extends LogEventPatternConverter { /** * Singleton. */ - private static final Log4j1NdcPatternConverter INSTANCE = - new Log4j1NdcPatternConverter(); + private static final Log4j1NdcPatternConverter INSTANCE = new Log4j1NdcPatternConverter(); /** * Private constructor. 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..fc7488fa57a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/NameAbbreviator.java @@ -0,0 +1,341 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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/pattern/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/package-info.java new file mode 100644 index 00000000000..dea0a473e4e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/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. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.pattern; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..5841920d674 --- /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) { + final Object msg = source.getMessage(); + if (msg instanceof MapMessage || msg instanceof Map) { + final Map props = + source.getProperties() != null ? new HashMap<>(source.getProperties()) : new HashMap<>(); + @SuppressWarnings("unchecked") + final 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; + final 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 { + final LocationInfo info = source.getLocationInformation(); + final StackTraceElement element = new StackTraceElement( + info.getClassName(), + info.getMethodName(), + info.getFileName(), + Integer.parseInt(info.getLineNumber())); + final Thread thread = getThread(source.getThreadName()); + final long threadId = thread != null ? thread.getId() : 0; + final 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) + .build(); + } + return new LogEventAdapter(event); + } + return source; + } + + private Thread getThread(final 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..1fd4c9fcd59 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.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.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(final String properties) { + final Map newMap = new HashMap<>(); + final StringTokenizer pairs = new StringTokenizer(properties, ","); + while (pairs.hasMoreTokens()) { + final 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()) { + final 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 { + final LocationInfo info = source.getLocationInformation(); + final StackTraceElement element = new StackTraceElement( + info.getClassName(), + info.getMethodName(), + info.getFileName(), + Integer.parseInt(info.getLineNumber())); + final Thread thread = getThread(source.getThreadName()); + final long threadId = thread != null ? thread.getId() : 0; + final 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) + .build(); + } + return new LogEventAdapter(event); + } + return source; + } + + private Thread getThread(final 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..36b051fc0c5 --- /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/rewrite/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/package-info.java new file mode 100644 index 00000000000..154ce83725b --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/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. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.rewrite; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..8bc4a43f803 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.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.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..a3f790ec5b1 --- /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..5b51f5dd7d8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/DefaultRepositorySelector.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.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..b18367feaa6 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorCode.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.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..5e94d343b38 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 @@ -1,25 +1,24 @@ /* * 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.spi; import org.apache.log4j.Appender; import org.apache.log4j.Logger; - /** * Appenders may delegate their error handling to * ErrorHandlers. @@ -27,11 +26,6 @@ * Error handling is a particularly tedious to get right because by * definition errors are hard to predict and to reproduce. *

- *

- * Please take the time to contact the author in case you discover - * that errors are not properly handled. You are most welcome to - * suggest new error handling policies or criticize existing policies. - *

*/ public interface ErrorHandler { @@ -46,10 +40,9 @@ public interface ErrorHandler { */ void setLogger(Logger logger); - /** * 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. @@ -95,4 +88,3 @@ public interface ErrorHandler { */ void setBackupAppender(Appender appender); } - 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..12026824a9f 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 @@ -1,18 +1,18 @@ /* * 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.spi; @@ -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,14 +57,16 @@ 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 } - /** *

If the decision is DENY, then the event will be * dropped. If the decision is NEUTRAL, then the next @@ -82,5 +94,4 @@ public void setNext(final Filter next) { public Filter getNext() { return next; } - } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java index 286ba541a03..db39828f6a1 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java @@ -1,18 +1,18 @@ /* * 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.spi; @@ -20,10 +20,10 @@ import org.apache.log4j.Category; /** - Listen to events occurring within a Hierarchy. - - @since 1.2 - + * Listen to events occurring within a Hierarchy. + * + * @since 1.2 + * */ public interface HierarchyEventListener { 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..d705b856f55 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LocationInfo.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.log4j.spi; + +import java.io.Serializable; +import java.util.Objects; +import org.apache.logging.log4j.core.util.Integers; + +/** + * 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 static final 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; + } + } + if (declaringClass != null && methodName != null) { + this.stackTraceElement = new StackTraceElement(declaringClass, methodName, file, Integers.parseInt(line)); + this.fullInfo = stackTraceElement.toString(); + } else { + this.stackTraceElement = null; + this.fullInfo = null; + } + } + + /** + * Gets the fully qualified class name of the caller making the logging request. + */ + public String getClassName() { + return stackTraceElement != null ? stackTraceElement.getClassName() : NA; + } + + /** + * Gets the file name of the caller. + */ + public String getFileName() { + return stackTraceElement != null ? stackTraceElement.getFileName() : NA; + } + + /** + * Gets the line number of the caller. + */ + public String getLineNumber() { + return stackTraceElement != null ? Integer.toString(stackTraceElement.getLineNumber()) : NA; + } + + /** + * Gets the method name of the caller. + */ + public String getMethodName() { + return stackTraceElement != null ? stackTraceElement.getMethodName() : NA; + } +} 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..1cca0912d4c 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 @@ -1,25 +1,24 @@ /* * 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.spi; import org.apache.log4j.Logger; /** - * * Implement this interface to create new instances of Logger or a sub-class of Logger. * *

@@ -29,5 +28,4 @@ public interface LoggerFactory { Logger makeNewLoggerInstance(String name); - } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerRepository.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerRepository.java index 812280f747b..3820f075d86 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerRepository.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerRepository.java @@ -1,23 +1,22 @@ /* * 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.spi; import java.util.Enumeration; - import org.apache.log4j.Appender; import org.apache.log4j.Category; import org.apache.log4j.Level; 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..71b9cb2246c 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 @@ -1,23 +1,243 @@ /* * 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.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.getJvmStartTime(); + } + + /** + * 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( + final String fqnOfCategoryClass, + final Category logger, + final Priority level, + final Object message, + final 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(final 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(final 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..c47a30f019e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLogger.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.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..77031c75070 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLoggerRepository.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.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/OptionHandler.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/OptionHandler.java index 1b855bc0ba9..b9ffe7e4d33 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/OptionHandler.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/OptionHandler.java @@ -1,22 +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. */ package org.apache.log4j.spi; - /** * Log4j 1 Interface for dealing with configuration. Ignored in Log4j 2. */ 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..9a89ccc987f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RendererSupport.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.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..460dc6993fe 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 @@ -1,42 +1,42 @@ /* * 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.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..9773bb246a8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RootLogger.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.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(final 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(final 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..a6acbed90e3 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableInformation.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.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..efaab324a22 --- /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..5c28778808b --- /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..9f9a95c9997 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/TriggeringEventEvaluator.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.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/spi/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/package-info.java index a7648dc661f..4aeced5ea93 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/package-info.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/package-info.java @@ -17,4 +17,9 @@ /** * Log4j 1.x compatibility layer. */ +@Export +@Version("2.20.1") package org.apache.log4j.spi; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..901ceeb7132 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/DenyAllFilter.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.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..593f0411d8f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/FallbackErrorHandler.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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..fb7cb77ccbf --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelMatchFilter.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.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..546d2fa409f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelRangeFilter.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.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..4733c5a2a17 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/NullAppender.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.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..d1b646e015d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/ReloadingPropertyConfigurator.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.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..b1d60188a26 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/StringMatchFilter.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.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/varia/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/package-info.java new file mode 100644 index 00000000000..c8c79ec86a2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/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. + */ +@Export +@Version("2.20.1") +package org.apache.log4j.varia; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..5aff69da56f 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 @@ -1,80 +1,187 @@ /* * 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.xml; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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.LogLog; +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.ConfigurationFactory; +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.apache.logging.log4j.util.PropertiesUtil; 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) { + private static boolean isFullCompatibilityEnabled() { + return PropertiesUtil.getProperties().getBooleanProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL); } - public void doConfigure(final URL url, final LoggerRepository repository) { + private static void warnFullCompatibilityDisabled() { + LogLog.warn( + "Ignoring `DOMConfigurator` call, since `log4j1.compatibility` is not enabled.\n" + + "See https://logging.staged.apache.org/log4j/2.x/migrate-from-log4j1.html#log4j1.compatibility for details."); } - public void doConfigure(final InputStream inputStream, final LoggerRepository repository) - throws FactoryConfigurationError { + public static void configure(final Element element) {} + + @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "The filename comes from a system property.") + public static void configure(final String fileName) throws FactoryConfigurationError { + if (isFullCompatibilityEnabled()) { + 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); + } + } else { + warnFullCompatibilityDisabled(); + } } - public void doConfigure(final Reader reader, final LoggerRepository repository) - throws FactoryConfigurationError { + public static void configure(final URL url) throws FactoryConfigurationError { + new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository()); } - public void doConfigure(final Element element, final LoggerRepository repository) { + public static void configureAndWatch(final String fileName) { + // TODO Watch + configure(fileName); } - public static void configure(final Element element) { + public static void configureAndWatch(final String fileName, final long delay) { + if (isFullCompatibilityEnabled()) { + final XMLWatchdog xdog = new XMLWatchdog(fileName); + xdog.setDelay(delay); + xdog.start(); + } else { + warnFullCompatibilityDisabled(); + } } - public static void configureAndWatch(final String configFilename) { + public static Object parseElement( + final Element element, final Properties props, @SuppressWarnings("rawtypes") final Class expectedClass) { + return null; } - public static void configureAndWatch(final String configFilename, final long delay) { - } + public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {} - public static void configure(final String filename) throws FactoryConfigurationError { + public static String subst(final String value, final Properties props) { + return OptionConverter.substVars(value, props); } - public static void configure(final URL url) throws FactoryConfigurationError { + private void doConfigure(final ConfigurationSource source) { + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + final Configuration configuration = new XmlConfigurationFactory().getConfiguration(context, source); + Configurator.reconfigure(configuration); } - public static String subst(final String value, final Properties props) { - return value; + public void doConfigure(final Element element, final LoggerRepository repository) {} + + public void doConfigure(final InputStream inputStream, final LoggerRepository repository) + throws FactoryConfigurationError { + if (isFullCompatibilityEnabled()) { + try { + doConfigure(new ConfigurationSource(inputStream)); + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } + } else { + warnFullCompatibilityDisabled(); + } } - public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) { + public void doConfigure(final Reader reader, final LoggerRepository repository) throws FactoryConfigurationError { + if (isFullCompatibilityEnabled()) { + 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); + } + } else { + warnFullCompatibilityDisabled(); + } + } + 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) { + if (isFullCompatibilityEnabled()) { + try { + final URLConnection connection = UrlConnectionFactory.createConnection(url); + try (final InputStream inputStream = connection.getInputStream()) { + doConfigure(new ConfigurationSource(inputStream, url)); + } + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } + } else { + warnFullCompatibilityDisabled(); + } } } 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..1533fc89389 --- /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(final String publicId, final String systemId) { + if (systemId.endsWith("log4j.dtd") || PUBLIC_ID.equals(publicId)) { + final 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..99451054aaa --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.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.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; +} 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..6356cced5db --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XMLWatchdog.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.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()); + } +} 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..dd2a0312b06 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java @@ -0,0 +1,831 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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.appender.rolling.TriggeringPolicy; +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 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, final 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 { + final ConfigurationSource source = getConfigurationSource(); + final ParseAction action = new ParseAction() { + @Override + @SuppressFBWarnings( + value = "XXE_DOCUMENT", + justification = "The `DocumentBuilder` is configured to not resolve external entities.") + public Document parse(final DocumentBuilder parser) throws SAXException, IOException { + @SuppressWarnings("resource") + final // 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) { + final Exception e = fce.getException(); + LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e); + throw fce; + } + + try { + dbf.setValidating(true); + + final DocumentBuilder docBuilder = dbf.newDocumentBuilder(); + + docBuilder.setErrorHandler(new SAXErrorHandler()); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + + final 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) { + final 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 { + final String clazz = subst(element.getAttribute("class"), props); + final Object instance = OptionConverter.instantiateByClassName(clazz, expectedClass, null); + + if (instance != null) { + final PropertySetter propSetter = new PropertySetter(instance); + final NodeList children = element.getChildNodes(); + final int length = children.getLength(); + + for (int loop = 0; loop < length; loop++) { + final Node currentNode = children.item(loop); + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + final Element currentElement = (Element) currentNode; + final 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(final Document doc, final String appenderName) { + Appender appender = appenderMap.get(appenderName); + + if (appender != null) { + return appender; + } + // Endre's hack: + Element element = null; + final NodeList list = doc.getElementsByTagName("appender"); + for (int t = 0; t < list.getLength(); t++) { + final Node node = list.item(t); + final NamedNodeMap map = node.getAttributes(); + final 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(final Element appenderRef) { + final String appenderName = subst(appenderRef.getAttribute(REF_ATTR)); + final 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(final Element appenderElement) { + final 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(final String className, final Element appenderElement) { + try { + final Appender appender = LoaderUtil.newInstanceOf(className); + final 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: + final String refName = subst(currentElement.getAttribute(REF_ATTR)); + if (appender instanceof AppenderAttachable) { + final AppenderAttachable aa = (AppenderAttachable) appender; + final 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) { + final 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(final Element rewritePolicyElement) { + final 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 { + final RewritePolicy policy = LoaderUtil.newInstanceOf(className); + final 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) { + final 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) { + final ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName( + subst(element.getAttribute(CLASS_ATTR)), ErrorHandler.class, null); + + if (eh != null) { + eh.setAppender(appender); + + final PropertySetter propSetter = new PropertySetter(eh); + forEachElement(element.getChildNodes(), currentElement -> { + final 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. + */ + 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(final Element filterElement) { + final 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 { + final Filter filter = LoaderUtil.newInstanceOf(className); + final 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) { + final 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(final Element loggerElement) { + // Create a new org.apache.log4j.Category object from the element. + final String catName = subst(loggerElement.getAttribute(NAME_ATTR)); + final 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(final Element rootElement) { + final 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: { + final Appender appender = findAppenderByReference(currentElement); + final 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(final Element layoutElement) { + final 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(final String className, final Element layout_element) { + try { + final Layout layout = LoaderUtil.newInstanceOf(className); + final PropertySetter propSetter = new PropertySetter(layout); + forEachElement(layout_element.getChildNodes(), currentElement -> { + final 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) { + final 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; + } + + public TriggeringPolicy parseTriggeringPolicy(final Element policyElement) { + final String className = subst(policyElement.getAttribute(CLASS_ATTR)); + LOGGER.debug("Parsing triggering policy of class: \"{}\"", className); + return manager.parseTriggeringPolicy(className, policyElement, this); + } + + /** + * Used internally to parse a level element. + */ + private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) { + String catName = logger.getName(); + if (isRoot) { + catName = "root"; + } + + final 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 { + final 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) { + final 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) { + final 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; + } + } + + final 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.isEmpty() && !debugAttrib.equals("null")) { + status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error"; + } else { + LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute."); + } + + final String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR)); + if (!confDebug.isEmpty() && !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().withStatus(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: + final 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(final NodeList list, final 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(final 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..a5b10fce848 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.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.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.core.config.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Constructs a Configuration usable in Log4j 2 from a Log4j 1 configuration file. + */ +@Plugin(name = "Log4j1XmlConfigurationFactory", category = ConfigurationFactory.CATEGORY) +@Order(2) +public class XmlConfigurationFactory extends ConfigurationFactory { + + public static final String FILE_EXTENSION = ".xml"; + + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + /** + * 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(final LoggerContext loggerContext, final ConfigurationSource source) { + final 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/java/org/apache/log4j/xml/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/package-info.java index e3ed0d16bdd..b7c3d31598d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/package-info.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/package-info.java @@ -17,4 +17,9 @@ /** * Log4j 1.x compatibility layer. */ +@Export +@Version("2.20.2") package org.apache.log4j.xml; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.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 deleted file mode 100644 index a80e6873f6d..00000000000 --- a/log4j-1.2-api/src/site/markdown/index.md +++ /dev/null @@ -1,34 +0,0 @@ - - - -# Log4j 1.2 Bridge - -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. -For more information, see [Runtime Dependencies](../runtime-dependencies.html). - -## Usage - -To use the Log4j Legacy Bridge just remove all the Log4j 1.x jars from the application and replace them -with the bridge jar. Once in place all logging that uses Log4j 1.x will be routed to Log4j 2. However, -applications that attempt to modify legacy Log4j by adding Appenders, Filters, etc may experience problems -if they try to verify the success of these actions as these methods are largely no-ops. diff --git a/log4j-1.2-api/src/site/site.xml b/log4j-1.2-api/src/site/site.xml deleted file mode 100644 index b27991ccafa..00000000000 --- a/log4j-1.2-api/src/site/site.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - 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..afb282b9c65 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 @@ -1,23 +1,22 @@ /* * 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 java.net.URI; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -33,7 +32,7 @@ public class BasicConfigurationFactory extends ConfigurationFactory { @Override public String[] getSupportedTypes() { - return new String[] { "*" }; + return new String[] {"*"}; } @Override @@ -42,11 +41,12 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final C } @Override - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + public Configuration getConfiguration( + final LoggerContext loggerContext, final String name, final URI configLocation) { return new BasicConfiguration(loggerContext); } - public class BasicConfiguration extends AbstractConfiguration { + public static class BasicConfiguration extends AbstractConfiguration { private static final long serialVersionUID = -2716784321395089563L; @@ -58,13 +58,12 @@ public BasicConfiguration(final LoggerContext loggerContext) { final LoggerConfig root = getRootLogger(); setName("BasicConfiguration"); final String levelName = System.getProperty(DEFAULT_LEVEL); - final Level level = (levelName != null && Level.getLevel(levelName) != null) ? Level.getLevel(levelName) - : Level.DEBUG; + final Level level = + (levelName != null && Level.getLevel(levelName) != null) ? Level.getLevel(levelName) : Level.DEBUG; root.setLevel(level); } @Override - protected void doConfigure() { - } + protected void doConfigure() {} } } 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..ab632507dc0 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.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; + +import org.apache.log4j.varia.NullAppender; +import org.junit.jupiter.api.Test; + +/** + * Test {@link BasicConfigurator}. + */ +class BasicConfiguratorTest { + + @Test + void testConfigure() { + // TODO More... + BasicConfigurator.configure(); + } + + @Test + void testResetConfiguration() { + // TODO More... + BasicConfigurator.resetConfiguration(); + } + + @Test + void testConfigureAppender() { + BasicConfigurator.configure(null); + // TODO More... + } + + @Test + void testConfigureConsoleAppender() { + // TODO What to do? Map to Log4j 2 Appender deeper in the code? + BasicConfigurator.configure(new ConsoleAppender()); + } + + @Test + 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..1212fc97c0c 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 @@ -1,65 +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.log4j; - -import static org.junit.Assert.assertEquals; - -import java.util.List; - -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.ClassRule; -import org.junit.Test; - -public class CallerInformationTest { - - // config from log4j-core test-jar - private static final String CONFIG = "log4j2-calling-class.xml"; - - @ClassRule - public static final LoggerContextRule ctx = new LoggerContextRule(CONFIG); - - @Test - public void testClassLogger() throws Exception { - final ListAppender app = ctx.getListAppender("Class").clear(); - final Logger logger = Logger.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 = ctx.getListAppender("Method").clear(); - final Logger logger = Logger.getLogger("MethodLogger"); - logger.info("More messages."); - logger.warn("CATASTROPHE INCOMING!"); - logger.error("ZOMBIES!!!"); - logger.warn("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); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.List; +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; + +// config from log4j-core test-jar +@LoggerContextSource(value = "log4j2-calling-class.xml") +class CallerInformationTest { + + @Test + void testClassLogger(@Named("Class") final ListAppender app) { + app.clear(); + final Logger logger = Logger.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 + void testMethodLogger(@Named("Method") final ListAppender app) { + app.clear(); + final Logger logger = Logger.getLogger("MethodLogger"); + logger.info("More messages."); + logger.warn("CATASTROPHE INCOMING!"); + logger.error("ZOMBIES!!!"); + logger.warn("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-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..6dd7305c07e 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 @@ -2,7 +2,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 * @@ -14,87 +14,146 @@ * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.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.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; /** * Tests of Category. */ -public class CategoryTest { +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 final ListAppender appender = new ListAppender(VERSION2_APPENDER_NAME); + private static final org.apache.log4j.ListAppender version1Appender = new org.apache.log4j.ListAppender(); - @BeforeClass - public static void setupClass() { + @BeforeAll + static void setupAll() { appender.start(); + version1Appender.setName(VERSION1_APPENDER_NAME); ConfigurationFactory.setConfigurationFactory(cf); LoggerContext.getContext().reconfigure(); } - @AfterClass - public static void cleanupClass() { + @AfterAll + static void cleanupAll() { ConfigurationFactory.removeConfigurationFactory(cf); appender.stop(); } - @Before - public void before() { + @BeforeEach + void before() { appender.clear(); } - + + @Test + void testExist() { + assertNull(Category.exists("Does not exist for sure")); + } + /** * Tests Category.forcedLog. */ @Test @SuppressWarnings("deprecation") - public void testForcedLog() { + void testForcedLog() { final MockCategory category = new MockCategory("org.example.foo"); category.setAdditivity(false); - category.getLogger().addAppender(appender); + category.setHierarchy(LogManager.getHierarchy()); + ((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"); 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 + assertInstanceOf(SimpleMessage.class, msg, "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(); + assertEquals(1, events, "Number of events"); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertInstanceOf(MapMessage.class, msg, "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(); + assertEquals(1, events, "Number of events"); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertInstanceOf(MapMessage.class, msg, "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); + assertEquals(1, events, "Number of events"); event = list.get(0); msg = event.getMessage(); - assertNotNull("No message", msg); - assertTrue("Incorrect Message type", msg instanceof ObjectMessage); + assertNotNull(msg, "No message"); + assertInstanceOf(ObjectMessage.class, msg, "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(); + assertEquals(1, events, "Number of events"); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertInstanceOf(SimpleMessage.class, msg, "Incorrect Message type"); + assertEquals("Hello, World", msg.getFormat()); appender.clear(); } @@ -104,40 +163,40 @@ public void testForcedLog() { * @throws Exception thrown if Category.getChainedPriority can not be found. */ @Test - public void testGetChainedPriorityReturnType() throws Exception { + void testGetChainedPriorityReturnType() throws Exception { final Method method = Category.class.getMethod("getChainedPriority", (Class[]) null); - assertTrue(method.getReturnType() == Priority.class); + assertEquals(Priority.class, method.getReturnType()); } /** * Tests l7dlog(Priority, String, Throwable). */ @Test - public void testL7dlog() { + void testL7dlog() { final Logger logger = Logger.getLogger("org.example.foo"); logger.setLevel(Level.ERROR); final Priority debug = Level.DEBUG; logger.l7dlog(debug, "Hello, World", null); - assertTrue(appender.getEvents().size() == 0); + assertTrue(appender.getEvents().isEmpty()); } /** * Tests l7dlog(Priority, String, Object[], Throwable). */ @Test - public void testL7dlog4Param() { + 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()); } /** * Test using a pre-existing Log4j 2 logger */ @Test - public void testExistingLog4j2Logger() { + void testExistingLog4j2Logger() { // create the logger using LogManager org.apache.logging.log4j.LogManager.getLogger("existingLogger"); // Logger will be the one created above @@ -148,8 +207,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()); } /** @@ -159,28 +218,195 @@ public void testExistingLog4j2Logger() { */ @Deprecated @Test - public void testSetPriority() { + void testSetPriority() { final Logger logger = Logger.getLogger("org.example.foo"); final Priority debug = Level.DEBUG; logger.setPriority(debug); } + /** + * Tests setPriority(Priority). + * + * @deprecated + */ + @Deprecated + @Test + void testSetPriorityNull() { + Logger.getLogger("org.example.foo").setPriority(null); + } + @Test - public void testClassName() { + 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 + void testStringLog() { + final String payload = "payload"; + testMessageImplementation( + payload, SimpleMessage.class, message -> assertEquals(payload, message.getFormattedMessage())); + } + + @Test + 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 + 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 + 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); + @SuppressWarnings("unchecked") + final M typedMessage = (M) message; + messageTester.accept(typedMessage); + } + + @Test + 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 + 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"); + assertInstanceOf( + org.apache.log4j.ListAppender.class, rootAppenders.get(0), "appender is a v1 ListAppender"); + assertEquals( + VERSION1_APPENDER_NAME, + rootLogger.getAppender(VERSION1_APPENDER_NAME).getName(), + "explicitly named appender"); + final Appender v2ListAppender = rootLogger.getAppender(VERSION2_APPENDER_NAME); + assertInstanceOf(AppenderWrapper.class, v2ListAppender, "explicitly named appender"); + assertInstanceOf( + ListAppender.class, + ((AppenderWrapper) v2ListAppender).getAppender(), + "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..7fe9a08e29f --- /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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Used to test Log4j 1 support. + */ +class ConsoleAppenderTest { + + private ConsoleAppender consoleAppender; + + @BeforeEach + void beforeEach() { + consoleAppender = new ConsoleAppender(); + } + + @Test + void testFollow() { + // Only really care that it compiles, behavior is secondary at this level. + consoleAppender.setFollow(true); + assertTrue(consoleAppender.getFollow()); + } + + @Test + 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..53596ed7c38 --- /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..b14c6114ff4 --- /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..965287f9284 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomFileAppender.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; + +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..2c775960961 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomNoopAppender.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; + +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..e4c5a4a14ee --- /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..35c1b2cf1b3 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LayoutTest.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.log4j; + +import junit.framework.TestCase; +import org.apache.log4j.spi.LoggingEvent; + +/** + * 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. + * + */ + public void testFormat() { + final Logger logger = Logger.getLogger("org.apache.log4j.LayoutTest"); + final LoggingEvent event = + new LoggingEvent("org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null); + final 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..32b574b8de3 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 @@ -2,7 +2,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 * @@ -14,23 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.log4j; -import java.util.Locale; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Locale; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.util.SerializationTestHelper; -import org.junit.Test; - -import static org.junit.Assert.*; - +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests of Level. * * @since 1.2.12 */ -public class LevelTest { +class LevelTest { /** * Serialize Level.INFO and check against witness. @@ -38,11 +40,10 @@ public class LevelTest { * @throws Exception if exception during test. */ @Test - public void testSerializeINFO() throws Exception { - final int[] skip = new int[]{}; + void testSerializeINFO() throws Exception { + final int[] skip = new int[] {}; SerializationTestHelper.assertSerializationEquals( - "target/test-classes/witness/serialization/info.bin", - Level.INFO, skip, Integer.MAX_VALUE); + "target/test-classes/witness/serialization/info.bin", Level.INFO, skip, Integer.MAX_VALUE); } /** @@ -51,17 +52,16 @@ public void testSerializeINFO() throws Exception { * @throws Exception if exception during test. */ @Test - public void testDeserializeINFO() throws Exception { + void testDeserializeINFO() throws Exception { final Object obj = - SerializationTestHelper.deserializeStream( - "target/test-classes/witness/serialization/info.bin"); - assertTrue(obj instanceof Level); + SerializationTestHelper.deserializeStream("target/test-classes/witness/serialization/info.bin"); + assertInstanceOf(Level.class, obj); final Level info = (Level) obj; assertEquals("INFO", info.toString()); // // JDK 1.1 doesn't support readResolve necessary for the assertion if (!System.getProperty("java.version").startsWith("1.1.")) { - assertTrue(obj == Level.INFO); + assertEquals(Level.INFO, obj); } } @@ -72,15 +72,16 @@ public void testDeserializeINFO() throws Exception { * @throws Exception if exception during test. */ @Test - public void testCustomLevelSerialization() throws Exception { + void testCustomLevelSerialization() throws Exception { final CustomLevel custom = new CustomLevel(); final Object obj = SerializationTestHelper.serializeClone(custom); - assertTrue(obj instanceof CustomLevel); + assertInstanceOf(CustomLevel.class, obj); final CustomLevel clone = (CustomLevel) obj; assertEquals(Level.INFO.level, clone.level); assertEquals(Level.INFO.levelStr, clone.levelStr); assertEquals(Level.INFO.syslogEquivalent, clone.syslogEquivalent); + assertEquals(OptionConverter.createLevel(custom), clone.version2Level); } /** @@ -97,8 +98,7 @@ private static class CustomLevel extends Level { * Create an instance of CustomLevel. */ public CustomLevel() { - super( - Level.INFO.level, Level.INFO.levelStr, Level.INFO.syslogEquivalent); + super(Level.INFO.level, Level.INFO.levelStr, Level.INFO.syslogEquivalent); } } @@ -106,7 +106,7 @@ public CustomLevel() { * Tests Level.TRACE_INT. */ @Test - public void testTraceInt() { + void testTraceInt() { assertEquals(5000, Level.TRACE_INT); } @@ -114,7 +114,7 @@ public void testTraceInt() { * Tests Level.TRACE. */ @Test - public void testTrace() { + void testTrace() { assertEquals("TRACE", Level.TRACE.toString()); assertEquals(5000, Level.TRACE.toInt()); assertEquals(7, Level.TRACE.getSyslogEquivalent()); @@ -124,7 +124,7 @@ public void testTrace() { * Tests Level.toLevel(Level.TRACE_INT). */ @Test - public void testIntToTrace() { + void testIntToTrace() { final Level trace = Level.toLevel(5000); assertEquals("TRACE", trace.toString()); } @@ -133,7 +133,7 @@ public void testIntToTrace() { * Tests Level.toLevel("TRACE"); */ @Test - public void testStringToTrace() { + void testStringToTrace() { final Level trace = Level.toLevel("TRACE"); assertEquals("TRACE", trace.toString()); } @@ -142,7 +142,7 @@ public void testStringToTrace() { * Tests that Level extends Priority. */ @Test - public void testLevelExtendsPriority() { + void testLevelExtendsPriority() { assertTrue(Priority.class.isAssignableFrom(Level.class)); } @@ -150,71 +150,80 @@ public void testLevelExtendsPriority() { * Tests Level.OFF. */ @Test - public void testOFF() { - assertTrue(Level.OFF instanceof Level); + void testOFF() { + assertInstanceOf(Level.class, Level.OFF); } /** * Tests Level.FATAL. */ @Test - public void testFATAL() { - assertTrue(Level.FATAL instanceof Level); + void testFATAL() { + assertInstanceOf(Level.class, Level.FATAL); } /** * Tests Level.ERROR. */ @Test - public void testERROR() { - assertTrue(Level.ERROR instanceof Level); + void testERROR() { + assertInstanceOf(Level.class, Level.ERROR); } /** * Tests Level.WARN. */ @Test - public void testWARN() { - assertTrue(Level.WARN instanceof Level); + void testWARN() { + assertInstanceOf(Level.class, Level.WARN); } /** * Tests Level.INFO. */ @Test - public void testINFO() { - assertTrue(Level.INFO instanceof Level); + void testINFO() { + assertInstanceOf(Level.class, Level.INFO); } /** * Tests Level.DEBUG. */ @Test - public void testDEBUG() { - assertTrue(Level.DEBUG instanceof Level); + void testDEBUG() { + assertInstanceOf(Level.class, Level.DEBUG); } /** * Tests Level.TRACE. */ @Test - public void testTRACE() { - assertTrue(Level.TRACE instanceof Level); + void testTRACE() { + assertInstanceOf(Level.class, Level.TRACE); } /** * Tests Level.ALL. */ @Test - public void testALL() { - assertTrue(Level.ALL instanceof Level); + void testALL() { + assertInstanceOf(Level.class, Level.ALL); + } + + /** + * Tests version2Level. + */ + @ParameterizedTest + @MethodSource("org.apache.log4j.helpers.OptionConverterLevelTest#standardLevels") + void testVersion2Level(final Level log4j1Level, final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level, log4j1Level.getVersion2Level()); } /** * Tests Level.toLevel(Level.All_INT). */ @Test - public void testIntToAll() { + void testIntToAll() { final Level level = Level.toLevel(Priority.ALL_INT); assertEquals("ALL", level.toString()); } @@ -223,17 +232,16 @@ public void testIntToAll() { * Tests Level.toLevel(Level.FATAL_INT). */ @Test - public void testIntToFatal() { + void testIntToFatal() { final Level level = Level.toLevel(Priority.FATAL_INT); assertEquals("FATAL", level.toString()); } - /** * Tests Level.toLevel(Level.OFF_INT). */ @Test - public void testIntToOff() { + void testIntToOff() { final Level level = Level.toLevel(Priority.OFF_INT); assertEquals("OFF", level.toString()); } @@ -242,7 +250,7 @@ public void testIntToOff() { * Tests Level.toLevel(17, Level.FATAL). */ @Test - public void testToLevelUnrecognizedInt() { + void testToLevelUnrecognizedInt() { final Level level = Level.toLevel(17, Level.FATAL); assertEquals("FATAL", level.toString()); } @@ -251,7 +259,7 @@ public void testToLevelUnrecognizedInt() { * Tests Level.toLevel(null, Level.FATAL). */ @Test - public void testToLevelNull() { + void testToLevelNull() { final Level level = Level.toLevel(null, Level.FATAL); assertEquals("FATAL", level.toString()); } @@ -260,7 +268,7 @@ public void testToLevelNull() { * Test that dotless lower I + "nfo" is recognized as INFO. */ @Test - public void testDotlessLowerI() { + void testDotlessLowerI() { final Level level = Level.toLevel("\u0131nfo"); assertEquals("INFO", level.toString()); } @@ -270,7 +278,7 @@ public void testDotlessLowerI() { * even in Turkish locale. */ @Test - public void testDottedLowerI() { + void testDottedLowerI() { final Locale defaultLocale = Locale.getDefault(); final Locale turkey = new Locale("tr", "TR"); Locale.setDefault(turkey); @@ -278,7 +286,4 @@ public void testDottedLowerI() { Locale.setDefault(defaultLocale); assertEquals("INFO", level.toString()); } - - } - 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..34bce86bd63 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/ListAppender.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.awaitility.Awaitility.waitAtMost; + +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(final LoggingEvent event) { + final Layout layout = getLayout(); + if (layout != null) { + final 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) { + waitAtMost(timeout, timeUnit).until(() -> messages.size() >= minSize); + 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..501d2c36709 --- /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.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link LogManager}. + */ +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 + 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..0e5165d4796 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 @@ -1,52 +1,53 @@ /* * 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 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.*; +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; /** * Test logging with MDC values. */ -public class LogWithMDCTest { - - private static final String CONFIG = "logWithMDC.xml"; - - @ClassRule - public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); +@LoggerContextSource("logWithMDC.xml") +class LogWithMDCTest { @Test - public void testMDC() throws Exception { + void testMDC(@Named("List") final ListAppender listApp) { 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"); + assertNotNull(listApp); + final List msgs = listApp.getMessages(); + assertNotNull(msgs, "No messages received"); + assertEquals(1, msgs.size()); + assertTrue(msgs.get(0).contains("Key1=John"), "Key1 is missing"); + assertTrue(msgs.get(0).contains("Key2=Smith"), "Key2 is missing"); + } 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..18ad15293c8 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 @@ -1,53 +1,53 @@ /* * 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 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.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; /** * Test passing MDC values to the Routing appender. */ -public class LogWithRouteTest { - - private static final String CONFIG = "log-RouteWithMDC.xml"; - - @ClassRule - public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); +@LoggerContextSource("log-RouteWithMDC.xml") +class LogWithRouteTest { @Test - public void testMDC() throws Exception { + void testMDC(@Named("List") final ListAppender listApp) { 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"); + 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..be16d3d0953 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerJira3410Test.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; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.Test; + +/** + * Tests Jira3410. + */ +class LoggerJira3410Test { + + @Test + void test() throws Exception { + try (final LoggerContext loggerContext = + TestConfigurator.configure("target/test-classes/log4j1-list.properties")) { + final Logger logger = LogManager.getLogger("test"); + // + final 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(listAppender, "No Message Appender"); + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages"); + 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.trim().endsWith(Long.MAX_VALUE + "=\"1\""), msg0); + // + // TODO Should be 1, not "1". + // TODO Should be null, not "null". + // TODO Where are the {} characters? + // TODO Where is the , characters? + assertTrue(msg1.trim().endsWith("null=\"null\" " + Long.MAX_VALUE + "=\"1\""), msg1); + // + assertTrue(msg2.trim().endsWith("{null=null, " + Long.MAX_VALUE + "=1}"), msg2); + } + } +} 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..f1f93599d8b 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 @@ -2,7 +2,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 * @@ -14,31 +14,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.log4j; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +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.Objects; import java.util.ResourceBundle; - import org.apache.logging.log4j.core.Appender; 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.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.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * Used for internal unit testing the Logger class. */ -public class LoggerTest { +class LoggerTest { Appender a1; Appender a2; @@ -52,28 +60,28 @@ public class LoggerTest { static ConfigurationFactory configurationFactory = new BasicConfigurationFactory(); - @BeforeClass - public static void setUpClass() { + @BeforeAll + static void setUpAll() { 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); } - @AfterClass - public static void tearDownClass() { + @AfterAll + static void tearDownAll() { ConfigurationFactory.removeConfigurationFactory(configurationFactory); } - @After - public void tearDown() { - LoggerContext.getContext().reconfigure(); + @BeforeEach + void resetTest() { + Objects.requireNonNull(LogManager.getHierarchy()).resetConfiguration(); a1 = null; a2 = null; } @@ -81,47 +89,47 @@ public void tearDown() { /** * Add an appender and see if it can be retrieved. * Skipping this test as the Appender interface isn't compatible with legacy Log4j. - public void testAppender1() { - logger = Logger.getLogger("test"); - a1 = new ListAppender("testAppender1"); - logger.addAppender(a1); - - Enumeration enumeration = logger.getAllAppenders(); - Appender aHat = (Appender) enumeration.nextElement(); - assertEquals(a1, aHat); - } */ + * public void testAppender1() { + * logger = Logger.getLogger("test"); + * a1 = new ListAppender("testAppender1"); + * logger.addAppender(a1); + * + * Enumeration enumeration = logger.getAllAppenders(); + * Appender aHat = (Appender) enumeration.nextElement(); + * assertEquals(a1, aHat); + * } */ /** * Add an appender X, Y, remove X and check if Y is the only * remaining appender. * Skipping this test as the Appender interface isn't compatible with legacy Log4j. - public void testAppender2() { - a1 = new FileAppender(); - a1.setName("testAppender2.1"); - a2 = new FileAppender(); - a2.setName("testAppender2.2"); - - logger = Logger.getLogger("test"); - logger.addAppender(a1); - logger.addAppender(a2); - logger.removeAppender("testAppender2.1"); - Enumeration enumeration = logger.getAllAppenders(); - Appender aHat = (Appender) enumeration.nextElement(); - assertEquals(a2, aHat); - assertTrue(!enumeration.hasMoreElements()); - } */ + * public void testAppender2() { + * a1 = new FileAppender(); + * a1.setName("testAppender2.1"); + * a2 = new FileAppender(); + * a2.setName("testAppender2.2"); + * + * logger = Logger.getLogger("test"); + * logger.addAppender(a1); + * logger.addAppender(a2); + * logger.removeAppender("testAppender2.1"); + * Enumeration enumeration = logger.getAllAppenders(); + * Appender aHat = (Appender) enumeration.nextElement(); + * assertEquals(a2, aHat); + * assertTrue(!enumeration.hasMoreElements()); + * } */ /** * Test if logger a.b inherits its appender from a. */ @Test - public void testAdditivity1() { + void testAdditivity1() { final Logger loggerA = Logger.getLogger("a"); final Logger loggerAB = Logger.getLogger("a.b"); final CountingAppender coutingAppender = new CountingAppender(); coutingAppender.start(); try { - loggerA.getLogger().addAppender(coutingAppender); + ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).addAppender(coutingAppender); assertEquals(0, coutingAppender.counter); loggerAB.debug(MSG); @@ -134,7 +142,7 @@ public void testAdditivity1() { assertEquals(4, coutingAppender.counter); coutingAppender.stop(); } finally { - loggerA.getLogger().removeAppender(coutingAppender); + ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).removeAppender(coutingAppender); } } @@ -142,7 +150,7 @@ public void testAdditivity1() { * Test multiple additivity. */ @Test - public void testAdditivity2() { + void testAdditivity2() { final Logger a = Logger.getLogger("a"); final Logger ab = Logger.getLogger("a.b"); final Logger abc = Logger.getLogger("a.b.c"); @@ -154,35 +162,36 @@ 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); + assertEquals(0, ca1.counter); + assertEquals(0, ca2.counter); ab.debug(MSG); - assertEquals(ca1.counter, 1); - assertEquals(ca2.counter, 0); + assertEquals(1, ca1.counter); + assertEquals(0, ca2.counter); abc.debug(MSG); - assertEquals(ca1.counter, 2); - assertEquals(ca2.counter, 1); + assertEquals(2, ca1.counter); + assertEquals(1, ca2.counter); x.debug(MSG); - assertEquals(ca1.counter, 2); - assertEquals(ca2.counter, 1); + assertEquals(2, ca1.counter); + assertEquals(1, ca2.counter); 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); + } + } /** * Test additivity flag. */ @Test - public void testAdditivity3() { + void testAdditivity3() { final Logger root = Logger.getRootLogger(); final Logger a = Logger.getLogger("a"); final Logger ab = Logger.getLogger("a.b"); @@ -196,38 +205,39 @@ 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); - assertEquals(caABC.counter, 0); + assertEquals(0, caRoot.counter); + assertEquals(0, caA.counter); + assertEquals(0, caABC.counter); ab.setAdditivity(false); a.debug(MSG); - assertEquals(caRoot.counter, 1); - assertEquals(caA.counter, 1); - assertEquals(caABC.counter, 0); + assertEquals(1, caRoot.counter); + assertEquals(1, caA.counter); + assertEquals(0, caABC.counter); ab.debug(MSG); - assertEquals(caRoot.counter, 1); - assertEquals(caA.counter, 1); - assertEquals(caABC.counter, 0); + assertEquals(1, caRoot.counter); + assertEquals(1, caA.counter); + assertEquals(0, caABC.counter); abc.debug(MSG); - assertEquals(caRoot.counter, 1); - assertEquals(caA.counter, 1); - assertEquals(caABC.counter, 1); + assertEquals(1, caRoot.counter); + assertEquals(1, caA.counter); + assertEquals(1, caABC.counter); caRoot.stop(); 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 public void testDisable1() { @@ -294,7 +304,7 @@ public void testDisable1() { } */ @Test - public void testRB1() { + void testRB1() { final Logger root = Logger.getRootLogger(); root.setResourceBundle(rbUS); ResourceBundle t = root.getResourceBundle(); @@ -313,11 +323,11 @@ public void testRB1() { } @Test - public void testRB2() { + void testRB2() { final Logger root = Logger.getRootLogger(); root.setResourceBundle(rbUS); ResourceBundle t = root.getResourceBundle(); - assertTrue(t == rbUS); + assertEquals(t, rbUS); final Logger x = Logger.getLogger("x"); final Logger x_y = Logger.getLogger("x.y"); @@ -333,11 +343,11 @@ public void testRB2() { } @Test - public void testRB3() { + void testRB3() { final Logger root = Logger.getRootLogger(); root.setResourceBundle(rbUS); ResourceBundle t = root.getResourceBundle(); - assertTrue(t == rbUS); + assertEquals(t, rbUS); final Logger x = Logger.getLogger("x"); final Logger x_y = Logger.getLogger("x.y"); @@ -354,7 +364,7 @@ public void testRB3() { } @Test - public void testExists() { + void testExists() { final Logger a = Logger.getLogger("a"); final Logger a_b = Logger.getLogger("a.b"); final Logger a_b_c = Logger.getLogger("a.b.c"); @@ -369,6 +379,7 @@ public void testExists() { t = LogManager.exists("a.b.c"); assertSame(a_b_c, t); } + /* Don't support hierarchy public void testHierarchy1() { Hierarchy h = new Hierarchy(new RootLogger((Level) Level.ERROR)); @@ -385,11 +396,11 @@ public void testHierarchy1() { * Tests logger.trace(Object). */ @Test - public void testTrace() { + 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,19 +416,19 @@ 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); } /** * Tests logger.trace(Object, Exception). */ @Test - public void testTraceWithException() { + void testTraceWithException() { final ListAppender appender = new ListAppender("List"); 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 +446,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); } } @@ -443,12 +454,12 @@ public void testTraceWithException() { * Tests isTraceEnabled. */ @Test - public void testIsTraceEnabled() { + void testIsTraceEnabled() { final ListAppender appender = new ListAppender("List"); 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 +469,82 @@ 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(); + void testLog() { + 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); + } + } + + @Test + void testSetLevel() { + final Logger a = Logger.getLogger("a"); + final Logger a_b = Logger.getLogger("a.b"); + final Logger a_b_c = Logger.getLogger("a.b.c"); + // test default for this test + assertThat(a.getLevel(), nullValue()); + assertThat(a_b.getLevel(), nullValue()); + assertThat(a_b_c.getLevel(), nullValue()); + assertThat(a.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + assertThat(a_b.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + assertThat(a_b_c.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + + // all + for (final Level level : + new Level[] {Level.DEBUG, Level.ERROR, Level.FATAL, Level.INFO, Level.TRACE, Level.WARN}) { + a.setLevel(level); + assertThat(a.getLevel(), is(equalTo(level))); + assertThat(a_b.getLevel(), nullValue()); + assertThat(a_b.getEffectiveLevel(), is(equalTo(level))); + assertThat(a_b_c.getLevel(), nullValue()); + assertThat(a_b_c.getEffectiveLevel(), is(equalTo(level))); + } + } + + @Test + void testSetPriority() { + final Logger a = Logger.getLogger("a"); + final Logger a_b = Logger.getLogger("a.b"); + final Logger a_b_c = Logger.getLogger("a.b.c"); + // test default for this test + assertThat(a.getPriority(), nullValue()); + assertThat(a_b.getPriority(), nullValue()); + assertThat(a_b_c.getPriority(), nullValue()); + + assertThat(a.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + assertThat(a_b.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + assertThat(a_b_c.getEffectiveLevel(), is(equalTo(Level.DEBUG))); + + // all + for (final Priority level : Level.getAllPossiblePriorities()) { + a.setPriority(level); + assertThat(a.getPriority(), is(equalTo(level))); + assertThat(a_b.getPriority(), nullValue()); + assertThat(a_b.getEffectiveLevel(), is(equalTo(level))); + assertThat(a_b.getPriority(), nullValue()); + assertThat(a_b_c.getEffectiveLevel(), is(equalTo(level))); } } @@ -507,7 +569,7 @@ private static class CountingAppender extends AbstractAppender { int counter; CountingAppender() { - super("Counter", null, null); + super("Counter", null, null, true, Property.EMPTY_ARRAY); counter = 0; } @@ -521,4 +583,3 @@ public boolean requiresLayout() { } } } - 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..0e77fd8962e 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 @@ -1,43 +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; - -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class LoggingTest { - - private static final String CONFIG = "log4j2-config.xml"; - - @ClassRule - public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); - - @Test - 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()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.assertNotNull; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +/** + * + */ +@LoggerContextSource("log4j2-config.xml") +class LoggingTest { + + @Test + void testParent() { + final Logger logger = Logger.getLogger("org.apache.test.logging.Test"); + final Category parent = logger.getParent(); + 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..b90b59ae671 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 @@ -2,12 +2,12 @@ * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed 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 - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,34 +16,34 @@ */ package org.apache.log4j; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class MDCTestCase { +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - @Before - public void setUp() { +class MDCTestCase { + + @BeforeEach + void setUp() { MDC.clear(); } - @After - public void tearDown() { + @AfterEach + void tearDown() { MDC.clear(); } @Test - public void testPut() throws Exception { + void testPut() { MDC.put("key", "some value"); - Assert.assertEquals("some value", MDC.get("key")); - Assert.assertEquals(1, MDC.getContext().size()); + assertEquals("some value", MDC.get("key")); + assertEquals(1, MDC.getContext().size()); } @Test - public void testRemoveLastKey() throws Exception { + 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..30173b5e2ee 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 @@ -1,36 +1,48 @@ /* * 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.jupiter.api.Assertions.assertEquals; + +import java.util.Stack; import org.apache.logging.log4j.util.Strings; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class NDCTest { +class NDCTest { @Test - public void testPopEmpty() { + void testPopEmpty() { NDC.clear(); - Assert.assertEquals(Strings.EMPTY, NDC.pop()); + assertEquals(Strings.EMPTY, NDC.pop()); } @Test - public void testPeekEmpty() { + void testPeekEmpty() { NDC.clear(); - Assert.assertEquals(Strings.EMPTY, NDC.peek()); + assertEquals(Strings.EMPTY, NDC.peek()); + } + + @SuppressWarnings({"rawtypes"}) + @Test + 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..7d649fdc9b5 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 @@ -2,7 +2,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 * @@ -14,26 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.log4j; -import java.util.Locale; - -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.Assert.*; +import java.util.Locale; +import java.util.stream.Stream; +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; /** * Tests of Priority. * */ -public class PriorityTest { +class PriorityTest { /** * Tests Priority.OFF_INT. */ @Test - public void testOffInt() { + void testOffInt() { assertEquals(Integer.MAX_VALUE, Priority.OFF_INT); } @@ -41,7 +45,7 @@ public void testOffInt() { * Tests Priority.FATAL_INT. */ @Test - public void testFatalInt() { + void testFatalInt() { assertEquals(50000, Priority.FATAL_INT); } @@ -49,7 +53,7 @@ public void testFatalInt() { * Tests Priority.ERROR_INT. */ @Test - public void testErrorInt() { + void testErrorInt() { assertEquals(40000, Priority.ERROR_INT); } @@ -57,7 +61,7 @@ public void testErrorInt() { * Tests Priority.WARN_INT. */ @Test - public void testWarnInt() { + void testWarnInt() { assertEquals(30000, Priority.WARN_INT); } @@ -65,7 +69,7 @@ public void testWarnInt() { * Tests Priority.INFO_INT. */ @Test - public void testInfoInt() { + void testInfoInt() { assertEquals(20000, Priority.INFO_INT); } @@ -73,7 +77,7 @@ public void testInfoInt() { * Tests Priority.DEBUG_INT. */ @Test - public void testDebugInt() { + void testDebugInt() { assertEquals(10000, Priority.DEBUG_INT); } @@ -81,17 +85,36 @@ public void testDebugInt() { * Tests Priority.ALL_INT. */ @Test - public void testAllInt() { + void testAllInt() { assertEquals(Integer.MIN_VALUE, Priority.ALL_INT); } + @SuppressWarnings("deprecation") + static Stream testVersion2Level() { + return Stream.of( + Arguments.of(Priority.FATAL, org.apache.logging.log4j.Level.FATAL), + Arguments.of(Priority.ERROR, org.apache.logging.log4j.Level.ERROR), + Arguments.of(Priority.WARN, org.apache.logging.log4j.Level.WARN), + Arguments.of(Priority.INFO, org.apache.logging.log4j.Level.INFO), + Arguments.of(Priority.DEBUG, org.apache.logging.log4j.Level.DEBUG)); + } + + /** + * Tests version2Level. + */ + @ParameterizedTest + @MethodSource() + void testVersion2Level(final Priority log4j1Priority, final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level, log4j1Priority.getVersion2Level()); + } + /** * Tests Priority.FATAL. */ @Test @SuppressWarnings("deprecation") - public void testFatal() { - assertTrue(Priority.FATAL instanceof Level); + void testFATAL() { + assertFalse(Priority.FATAL instanceof Level); } /** @@ -99,8 +122,8 @@ public void testFatal() { */ @Test @SuppressWarnings("deprecation") - public void testERROR() { - assertTrue(Priority.ERROR instanceof Level); + void testERROR() { + assertFalse(Priority.ERROR instanceof Level); } /** @@ -108,8 +131,8 @@ public void testERROR() { */ @Test @SuppressWarnings("deprecation") - public void testWARN() { - assertTrue(Priority.WARN instanceof Level); + void testWARN() { + assertFalse(Priority.WARN instanceof Level); } /** @@ -117,8 +140,8 @@ public void testWARN() { */ @Test @SuppressWarnings("deprecation") - public void testINFO() { - assertTrue(Priority.INFO instanceof Level); + void testINFO() { + assertFalse(Priority.INFO instanceof Level); } /** @@ -126,8 +149,8 @@ public void testINFO() { */ @Test @SuppressWarnings("deprecation") - public void testDEBUG() { - assertTrue(Priority.DEBUG instanceof Level); + void testDEBUG() { + assertFalse(Priority.DEBUG instanceof Level); } /** @@ -135,8 +158,8 @@ public void testDEBUG() { */ @Test @SuppressWarnings("deprecation") - public void testEqualsNull() { - assertFalse(Priority.DEBUG.equals(null)); + void testEqualsNull() { + assertNotEquals(null, Priority.DEBUG); } /** @@ -144,11 +167,11 @@ public void testEqualsNull() { */ @Test @SuppressWarnings("deprecation") - public void testEqualsLevel() { + void testEqualsLevel() { // // this behavior violates the equals contract. // - assertTrue(Priority.DEBUG.equals(Level.DEBUG)); + assertEquals(Priority.DEBUG, Level.DEBUG); } /** @@ -156,7 +179,7 @@ public void testEqualsLevel() { */ @Test @SuppressWarnings("deprecation") - public void testGetAllPossiblePriorities() { + void testGetAllPossiblePriorities() { final Priority[] priorities = Priority.getAllPossiblePriorities(); assertEquals(5, priorities.length); } @@ -166,8 +189,8 @@ public void testGetAllPossiblePriorities() { */ @Test @SuppressWarnings("deprecation") - public void testToPriorityString() { - assertTrue(Priority.toPriority("DEBUG") == Level.DEBUG); + void testToPriorityString() { + assertEquals(Level.DEBUG, Priority.toPriority("DEBUG")); } /** @@ -175,8 +198,8 @@ public void testToPriorityString() { */ @Test @SuppressWarnings("deprecation") - public void testToPriorityInt() { - assertTrue(Priority.toPriority(Priority.DEBUG_INT) == Level.DEBUG); + void testToPriorityInt() { + assertEquals(Level.DEBUG, Priority.toPriority(Priority.DEBUG_INT)); } /** @@ -184,8 +207,8 @@ public void testToPriorityInt() { */ @Test @SuppressWarnings("deprecation") - public void testToPriorityStringPriority() { - assertTrue(Priority.toPriority("foo", Priority.DEBUG) == Priority.DEBUG); + void testToPriorityStringPriority() { + assertEquals(Priority.DEBUG, Priority.toPriority("foo", Priority.DEBUG)); } /** @@ -193,8 +216,8 @@ public void testToPriorityStringPriority() { */ @Test @SuppressWarnings("deprecation") - public void testToPriorityIntPriority() { - assertTrue(Priority.toPriority(17, Priority.DEBUG) == Priority.DEBUG); + void testToPriorityIntPriority() { + assertEquals(Priority.DEBUG, Priority.toPriority(17, Priority.DEBUG)); } /** @@ -202,7 +225,7 @@ public void testToPriorityIntPriority() { */ @Test @SuppressWarnings("deprecation") - public void testDotlessLowerI() { + void testDotlessLowerI() { final Priority level = Priority.toPriority("\u0131nfo"); assertEquals("INFO", level.toString()); } @@ -213,14 +236,12 @@ public void testDotlessLowerI() { */ @Test @SuppressWarnings("deprecation") - public void testDottedLowerI() { + void testDottedLowerI() { final Locale defaultLocale = Locale.getDefault(); final Locale turkey = new Locale("tr", "TR"); Locale.setDefault(turkey); final Priority level = Priority.toPriority("info"); Locale.setDefault(defaultLocale); assertEquals("INFO", level.toString()); - } - + } } - 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..b80296f898f --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java @@ -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. + */ +package org.apache.log4j; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +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 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.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.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertyConfigurator}. + */ +@SetTestProperty(key = "log4j1.compatibility", value = "true") +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 BAD_ESCAPE_PROPERTIES = "/PropertyConfiguratorTest/badEscape.properties"; + private static final String FILTER_PROPERTIES = "/PropertyConfiguratorTest/filter.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"; + + @AfterEach + void cleanup() { + LogManager.resetConfiguration(); + } + + /** + * Test for bug 40944. Did not catch IllegalArgumentException on Properties.load and close input stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + void testBadUnicodeEscape() throws IOException { + try (final InputStream is = PropertyConfiguratorTest.class.getResourceAsStream(BAD_ESCAPE_PROPERTIES)) { + PropertyConfigurator.configure(is); + } + } + + /** + * Tests configuring Log4J from an InputStream. + * + * @since 1.2.17 + */ + @Test + void testInputStream() throws IOException { + try (final InputStream inputStream = PropertyConfiguratorTest.class.getResourceAsStream(FILTER_PROPERTIES)) { + PropertyConfigurator.configure(inputStream); + + final Logger rootLogger = Logger.getRootLogger(); + assertThat(rootLogger.getLevel(), is(equalTo(Level.INFO))); + assertThat(rootLogger.getAppender("CONSOLE"), notNullValue()); + final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest"); + assertThat(logger.getLevel(), is(equalTo(Level.DEBUG))); + assertThat(logger.getAppender("ROLLING"), notNullValue()); + } + } + + /** + * Test for bug 47465. configure(URL) did not close opened JarURLConnection. + * + * @throws IOException if IOException creating properties jar. + */ + @Test + void testJarURL() throws IOException { + final File dir = new File("output"); + dir.mkdirs(); + final File file = new File("output/properties.jar"); + try (final 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 + 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()); + } + + /** + * Test processing of log4j.reset property, see bug 17531. + */ + @Test + 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")); + } + + /** + * Test for bug 40944. configure(URL) never closed opened stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + void testURL() throws IOException { + final File file = new File("target/unclosed.properties"); + try (final 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. + * + */ + @Test + void testURLBadEscape() { + final URL configURL = PropertyConfiguratorTest.class.getResource(BAD_ESCAPE_PROPERTIES); + PropertyConfigurator.configure(configURL); + } + + @Test + @SetTestProperty(key = "log4j1.compatibility", value = "false") + void when_compatibility_disabled_configurator_is_no_op() throws IOException { + final Logger rootLogger = Logger.getRootLogger(); + final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest"); + assertThat(logger.getLevel(), nullValue()); + try (final InputStream inputStream = PropertyConfiguratorTest.class.getResourceAsStream(FILTER_PROPERTIES)) { + PropertyConfigurator.configure(inputStream); + + assertThat(rootLogger.getAppender("CONSOLE"), nullValue()); + assertThat(rootLogger.getLevel(), is(not(equalTo(Level.INFO)))); + + assertThat(logger.getAppender("ROLLING"), nullValue()); + assertThat(logger.getLevel(), nullValue()); + } + } +} 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 deleted file mode 100644 index 99bd5a2c7c6..00000000000 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/VelocityTest.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.log4j; - -import java.io.StringWriter; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * Note that this test must clean up after itself or it may cause other tests to fail. - */ -public class VelocityTest { - -private static LoggerContext context; - - @BeforeClass - public static void setupClass() { - context = LoggerContext.getContext(false); - } - - @AfterClass - public static void tearDownClass() { - Configurator.shutdown(context); - StatusLogger.getLogger().reset(); - } - - @Test - public void testVelocity() { - Velocity.init(); - final VelocityContext vContext = new VelocityContext(); - vContext.put("name", new String("Velocity")); - - final Template template = Velocity.getTemplate("target/test-classes/hello.vm"); - - final StringWriter sw = new StringWriter(); - - template.merge(vContext, sw); - } -} 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..ff5faff5f1d --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.Test; + +class LogEventWrapperTest { + + @Test + void testThread() { + final Thread currentThread = Thread.currentThread(); + final String threadName = currentThread.getName(); + final LoggingEvent log4j1Event = new LoggingEvent() { + + @Override + public String getThreadName() { + return threadName; + } + }; + final LogEvent log4j2Event = new LogEventWrapper(log4j1Event); + assertEquals(currentThread.getId(), log4j2Event.getThreadId()); + assertEquals(currentThread.getPriority(), log4j2Event.getThreadPriority()); + } + + @Test + void testToImmutable() { + final 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..8d6b5a23f9d --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/BuilderManagerTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Properties; +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.junit.jupiter.api.Test; + +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 + void testReturnInvalidValueOnError() { + final PropertiesConfiguration config = new PropertiesConfiguration(null, null); + final BuilderManager manager = new BuilderManager(); + 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..dbed77a6ab5 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.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.builders; + +import static org.apache.log4j.builders.BuilderManager.CATEGORY; +import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG; +import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; +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.config.plugins.Plugin; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.w3c.dom.Element; + +/** + * Builder for the native Log4j 2.x list appender to be used in the tests. + */ +@Plugin(name = "org.apache.logging.log4j.test.appender.ListAppender", category = CATEGORY) +public class Log4j2ListAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + public Log4j2ListAppenderBuilder() {} + + public Log4j2ListAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element element, final 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( + 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); + return createAppender(name, layout, filter); + } + + private Appender createAppender(final String name, final Layout layout, final 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/filter/LevelRangeFilterBuilderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilderTest.java new file mode 100644 index 00000000000..4a6290da846 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilderTest.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.StringReader; +import java.util.Properties; +import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.log4j.bridge.FilterWrapper; +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.filter.LevelRangeFilter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; + +class LevelRangeFilterBuilderTest { + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testAcceptOnMatchTrue(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, true); + + assertResult(Result.DENY, levelRangeFilter, Level.ALL); + assertResult(Result.DENY, levelRangeFilter, Level.DEBUG); + assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO); + assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN); + assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR); + assertResult(Result.DENY, levelRangeFilter, Level.FATAL); + assertResult(Result.DENY, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testAcceptOnMatchFalse(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, false); + + assertResult(Result.DENY, levelRangeFilter, Level.ALL); + assertResult(Result.DENY, levelRangeFilter, Level.DEBUG); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.INFO); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.WARN); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.ERROR); + assertResult(Result.DENY, levelRangeFilter, Level.FATAL); + assertResult(Result.DENY, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testAcceptOnMatchNull(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.ERROR, null); + + assertResult(Result.DENY, levelRangeFilter, Level.ALL); + assertResult(Result.DENY, levelRangeFilter, Level.DEBUG); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.INFO); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.WARN); + assertResult(Result.NEUTRAL, levelRangeFilter, Level.ERROR); + assertResult(Result.DENY, levelRangeFilter, Level.FATAL); + assertResult(Result.DENY, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testMinLevelNull(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(null, Level.ERROR, true); + + assertResult(Result.ACCEPT, levelRangeFilter, Level.ALL); + assertResult(Result.ACCEPT, levelRangeFilter, Level.DEBUG); + assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO); + assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN); + assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR); + assertResult(Result.DENY, levelRangeFilter, Level.FATAL); + assertResult(Result.DENY, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testMaxLevelNull(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, null, true); + + assertResult(Result.DENY, levelRangeFilter, Level.ALL); + assertResult(Result.DENY, levelRangeFilter, Level.DEBUG); + assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO); + assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN); + assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR); + assertResult(Result.ACCEPT, levelRangeFilter, Level.FATAL); + assertResult(Result.ACCEPT, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testMinMaxLevelSame(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(Level.INFO, Level.INFO, true); + + assertResult(Result.DENY, levelRangeFilter, Level.ALL); + assertResult(Result.DENY, levelRangeFilter, Level.DEBUG); + assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO); + assertResult(Result.DENY, levelRangeFilter, Level.WARN); + assertResult(Result.DENY, levelRangeFilter, Level.ERROR); + assertResult(Result.DENY, levelRangeFilter, Level.FATAL); + assertResult(Result.DENY, levelRangeFilter, Level.OFF); + } + + @ParameterizedTest + @ArgumentsSource(TestLevelRangeFilterBuilderProvider.class) + void testMinMaxLevelNull(final TestLevelRangeFilterBuilder builder) throws Exception { + final LevelRangeFilter levelRangeFilter = builder.build(null, null, true); + + assertResult(Result.ACCEPT, levelRangeFilter, Level.ALL); + assertResult(Result.ACCEPT, levelRangeFilter, Level.DEBUG); + assertResult(Result.ACCEPT, levelRangeFilter, Level.INFO); + assertResult(Result.ACCEPT, levelRangeFilter, Level.WARN); + assertResult(Result.ACCEPT, levelRangeFilter, Level.ERROR); + assertResult(Result.ACCEPT, levelRangeFilter, Level.FATAL); + assertResult(Result.ACCEPT, levelRangeFilter, Level.OFF); + } + + private static void assertResult(final Result expected, final LevelRangeFilter filter, final Level level) { + assertSame(expected, filter.filter(null, level, null, (Object) null, null)); + } + + private static class TestLevelRangeFilterBuilderProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(final ExtensionContext extensionContext) { + return Stream.of( + Arguments.of(new TestLevelRangeFilterFromXmlBuilder()), + Arguments.of(new TestLevelRangeFilterFromPropertyBuilder())); + } + } + + private interface TestLevelRangeFilterBuilder { + + LevelRangeFilter build(Level levelMin, Level levelMax, Boolean acceptOnMatch) throws Exception; + } + + private static class TestLevelRangeFilterFromXmlBuilder implements TestLevelRangeFilterBuilder { + + @Override + public LevelRangeFilter build(final Level levelMin, final Level levelMax, final Boolean acceptOnMatch) + throws Exception { + final LevelRangeFilterBuilder builder = new LevelRangeFilterBuilder(); + final Filter filter = builder.parse(generateTestXml(levelMin, levelMax, acceptOnMatch), null); + final org.apache.logging.log4j.core.Filter wrappedFilter = ((FilterWrapper) filter).getFilter(); + return (LevelRangeFilter) wrappedFilter; + } + + private static Element generateTestXml(final Level levelMin, final Level levelMax, final Boolean acceptOnMatch) + throws Exception { + + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + if (levelMin != null) { + sb.append(String.format("\n", levelMin)); + } + if (levelMax != null) { + sb.append(String.format("\n", levelMax)); + } + if (acceptOnMatch != null) { + sb.append(String.format("\n", acceptOnMatch)); + } + sb.append(""); + + return DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(new InputSource(new StringReader(sb.toString()))) + .getDocumentElement(); + } + } + + private static class TestLevelRangeFilterFromPropertyBuilder implements TestLevelRangeFilterBuilder { + + @Override + public LevelRangeFilter build(final Level levelMin, final Level levelMax, final Boolean acceptOnMatch) { + final Properties properties = new Properties(); + if (levelMin != null) { + properties.setProperty("foobar.levelMin", levelMin.name()); + } + if (levelMax != null) { + properties.setProperty("foobar.levelMax", levelMax.name()); + } + if (acceptOnMatch != null) { + properties.setProperty("foobar.acceptOnMatch", acceptOnMatch.toString()); + } + final LevelRangeFilterBuilder builder = new LevelRangeFilterBuilder("foobar", properties); + final Filter filter = builder.parse(null); + final org.apache.logging.log4j.core.Filter wrappedFilter = ((FilterWrapper) filter).getFilter(); + return (LevelRangeFilter) wrappedFilter; + } + } +} 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..c64dfc3df37 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/layout/PatternLayoutBuilderTest.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.builders.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +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; + +class PatternLayoutBuilderTest { + + static Stream patterns() { + return Stream.of( + 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")); + } + + @ParameterizedTest + @MethodSource("patterns") + 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..f27722278dc 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,44 +1,44 @@ -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 + * contributor license agreements. See the NOTICE file distributed 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.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.jupiter.api.Test; +import org.xml.sax.SAXException; -@RunWith(Parameterized.class) public abstract class AbstractLog4j1ConfigurationConverterTest { protected static List getPaths(final String root) throws IOException { final List paths = new ArrayList<>(); Files.walkFileTree(Paths.get(root), new SimpleFileVisitor() { @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) { paths.add(file.toAbsolutePath()); return FileVisitResult.CONTINUE; } @@ -46,23 +46,34 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr return paths; } - private final Path pathIn; - - public AbstractLog4j1ConfigurationConverterTest(final Path path) { - super(); - this.pathIn = path; - } + public AbstractLog4j1ConfigurationConverterTest() {} @Test - public void test() throws IOException { + public void test(Path path) throws Exception { final Path tempFile = Files.createTempFile("log4j2", ".xml"); try { - final Log4j1ConfigurationConverter.CommandLineArguments cla = new Log4j1ConfigurationConverter.CommandLineArguments(); - cla.setPathIn(pathIn); + final Log4j1ConfigurationConverter.CommandLineArguments cla = + new Log4j1ConfigurationConverter.CommandLineArguments(); + cla.setPathIn(path); cla.setPathOut(tempFile); Log4j1ConfigurationConverter.run(cla); + checkWellFormedXml(tempFile); + checkUnnecessaryEscaping(tempFile); } finally { Files.deleteIfExists(tempFile); } } + + private void checkUnnecessaryEscaping(final Path tempFile) throws IOException { + for (String line : Files.readAllLines(tempFile)) { + assertFalse(line.endsWith(" ")); + } + } + + private void checkWellFormedXml(final 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..b9967d0de41 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java @@ -0,0 +1,662 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +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.junit.jupiter.api.io.TempDir; + +abstract class AbstractLog4j1ConfigurationTest { + + @TempDir + File tempDir; + + abstract Configuration getConfiguration(String configResourcePrefix) throws URISyntaxException, IOException; + + protected InputStream getResourceAsStream(final String configResource) { + final InputStream is = getClass().getClassLoader().getResourceAsStream(configResource); + assertNotNull(is); + return is; + } + + protected LoggerContext configure(final String configResourcePrefix) throws URISyntaxException, IOException { + Configurator.reconfigure(getConfiguration(configResourcePrefix)); + return (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + } + + void testConsoleCapitalization() throws Exception { + final Configuration config = getConfiguration("config-1.2/log4j-capitalization"); + final Appender capitalized = config.getAppender("ConsoleCapitalized"); + assertNotNull(capitalized); + assertEquals(ConsoleAppender.class, capitalized.getClass()); + final Appender javaStyle = config.getAppender("ConsoleJavaStyle"); + assertNotNull(javaStyle); + assertEquals(ConsoleAppender.class, javaStyle.getClass()); + testConsoleAppender((ConsoleAppender) capitalized, (ConsoleAppender) javaStyle); + } + + private void testConsoleAppender(final ConsoleAppender expected, final ConsoleAppender actual) { + assertEquals(expected.getImmediateFlush(), actual.getImmediateFlush(), "immediateFlush"); + assertEquals(expected.getTarget(), actual.getTarget(), "target"); + assertEquals(expected.getLayout().getClass(), actual.getLayout().getClass(), "layoutClass"); + if (expected.getLayout() instanceof PatternLayout) { + patternLayoutEquals((PatternLayout) expected.getLayout(), (PatternLayout) actual.getLayout()); + } + } + + private void patternLayoutEquals(final PatternLayout expected, final 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( + appender, "Missing appender '" + name + "' in configuration " + configResource + " → " + configuration); + assertTrue(getFollowProperty(appender), "follow"); + 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(); + } + + void testConsoleTtccLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-TTCCLayout"); + assertEquals("%d{ISO8601}{CET} %p - %m%n", layout.getConversionPattern()); + } + + void testRollingFileAppender() throws Exception { + testRollingFileAppender("config-1.2/log4j-RollingFileAppender"); + } + + void testDailyRollingFileAppender() throws Exception { + testDailyRollingFileAppender("config-1.2/log4j-DailyRollingFileAppender"); + } + + void testRollingFileAppenderWithProperties() throws Exception { + testRollingFileAppender("config-1.2/log4j-RollingFileAppender-with-props"); + } + + 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"); + assertFalse(getAppendProperty(appender), "append"); + assertEquals(1000, appender.getManager().getBufferSize(), "bufferSize"); + assertFalse(appender.getImmediateFlush(), "immediateFlush"); + final DefaultRolloverStrategy rolloverStrategy = + (DefaultRolloverStrategy) appender.getManager().getRolloverStrategy(); + assertEquals(16, rolloverStrategy.getMaxIndex()); + final CompositeTriggeringPolicy ctp = appender.getTriggeringPolicy(); + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertInstanceOf(SizeBasedTriggeringPolicy.class, tp, tp.getClass().getName()); + final SizeBasedTriggeringPolicy sbtp = (SizeBasedTriggeringPolicy) tp; + assertEquals(20 * 1024 * 1024, sbtp.getMaxFileSize()); + appender.stop(10, TimeUnit.SECONDS); + assertEquals(tempFileName, appender.getFileName()); + } finally { + configuration.start(); + configuration.stop(); + Files.deleteIfExists(tempFilePath); + } + } + + 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 + Path path = new File(appender.getFileName()).toPath(); + Files.deleteIfExists(path); + path = new File("${java.io.tmpdir}").toPath(); + Files.deleteIfExists(path); + } + + private void testRollingFileAppender(final String configResource) throws Exception { + final Configuration configuration = getConfiguration(configResource); + final Appender appender = configuration.getAppender("RFA"); + assertNotNull(appender); + assertEquals("RFA", appender.getName()); + assertInstanceOf( + RollingFileAppender.class, appender, appender.getClass().getName()); + final RollingFileAppender rfa = (RollingFileAppender) appender; + + assertInstanceOf( + DefaultRolloverStrategy.class, rfa.getManager().getRolloverStrategy(), "defaultRolloverStrategy"); + assertFalse(((DefaultRolloverStrategy) rfa.getManager().getRolloverStrategy()).isUseMax(), "rolloverStrategy"); + assertFalse(getAppendProperty(rfa), "append"); + assertEquals(1000, rfa.getManager().getBufferSize(), "bufferSize"); + assertFalse(rfa.getImmediateFlush(), "immediateFlush"); + assertEquals("target/hadoop.log", rfa.getFileName()); + assertEquals("target/hadoop.log.%i", rfa.getFilePattern()); + final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); + assertNotNull(triggeringPolicy); + assertInstanceOf( + CompositeTriggeringPolicy.class, + triggeringPolicy, + triggeringPolicy.getClass().getName()); + final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertInstanceOf(SizeBasedTriggeringPolicy.class, tp, tp.getClass().getName()); + final SizeBasedTriggeringPolicy sbtp = (SizeBasedTriggeringPolicy) tp; + assertEquals(256 * 1024 * 1024, sbtp.getMaxFileSize()); + final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); + assertInstanceOf( + DefaultRolloverStrategy.class, + rolloverStrategy, + rolloverStrategy.getClass().getName()); + final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; + assertEquals(20, drs.getMaxIndex()); + configuration.start(); + configuration.stop(); + } + + private void testDailyRollingFileAppender(final String configResource) throws Exception { + final Configuration configuration = getConfiguration(configResource); + try { + final Appender appender = configuration.getAppender("DRFA"); + assertNotNull(appender); + assertEquals("DRFA", appender.getName()); + assertInstanceOf( + RollingFileAppender.class, appender, appender.getClass().getName()); + final RollingFileAppender rfa = (RollingFileAppender) appender; + assertFalse(getAppendProperty(rfa), "append"); + assertEquals(1000, rfa.getManager().getBufferSize(), "bufferSize"); + assertFalse(rfa.getImmediateFlush(), "immediateFlush"); + assertEquals("target/hadoop.log", rfa.getFileName()); + assertEquals("target/hadoop.log%d{.dd-MM-yyyy}", rfa.getFilePattern()); + final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); + assertNotNull(triggeringPolicy); + assertInstanceOf( + CompositeTriggeringPolicy.class, + triggeringPolicy, + triggeringPolicy.getClass().getName()); + final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertInstanceOf(TimeBasedTriggeringPolicy.class, tp, tp.getClass().getName()); + final TimeBasedTriggeringPolicy tbtp = (TimeBasedTriggeringPolicy) tp; + assertEquals(1, tbtp.getInterval()); + final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); + assertInstanceOf( + DefaultRolloverStrategy.class, + rolloverStrategy, + rolloverStrategy.getClass().getName()); + final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; + assertEquals(Integer.MAX_VALUE, drs.getMaxIndex()); + } finally { + configuration.start(); + configuration.stop(); + } + } + + private Layout testFile() throws Exception { + final Configuration configuration = getConfiguration("config-1.2/log4j-file-SimpleLayout"); + 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()); + assertFalse(getAppendProperty(appender), "append"); + assertEquals(1000, appender.getManager().getBufferSize(), "bufferSize"); + assertFalse(appender.getImmediateFlush(), "immediateFlush"); + configuration.start(); + configuration.stop(); + return appender.getLayout(); + } + + 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()); + } + + void testConsoleHtmlLayout() throws Exception { + final HtmlLayout layout = (HtmlLayout) testConsole("config-1.2/log4j-console-HtmlLayout"); + assertEquals("Headline", layout.getTitle()); + assertTrue(layout.isLocationInfo()); + } + + 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()); + } + + void testConsoleSimpleLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-SimpleLayout"); + assertEquals("%v1Level - %m%n", layout.getConversionPattern()); + } + + void testFileSimpleLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testFile(); + assertEquals("%v1Level - %m%n", layout.getConversionPattern()); + } + + 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()); + assertInstanceOf(NullAppender.class, appender, appender.getClass().getName()); + } + + private boolean getFollowProperty(final ConsoleAppender consoleAppender) throws Exception { + OutputStream outputStream = getOutputStream(consoleAppender.getManager()); + String className = outputStream.getClass().getName(); + return className.endsWith("ConsoleAppender$SystemErrStream") + || className.endsWith("ConsoleAppender$SystemOutStream"); + } + + 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 8 + try { + final Field appendField = FileOutputStream.class.getDeclaredField("append"); + appendField.setAccessible(true); + return (Boolean) appendField.get(os); + } catch (NoSuchFieldError | NoSuchFieldException e) { + // 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( + appender, + "Missing appender '" + appenderName + "' in configuration " + config.getConfigurationSource()); + return appender.getLayout(); + } + + /** + * Test if the default values from Log4j 1.x are respected. + */ + void testDefaultValues() throws Exception { + final Configuration config = getConfiguration("config-1.2/log4j-defaultValues"); + // HtmlLayout + final HtmlLayout htmlLayout = (HtmlLayout) testLayout(config, "HTMLLayout"); + assertNotNull(htmlLayout); + assertEquals("Log4J Log Messages", htmlLayout.getTitle(), "title"); + assertFalse(htmlLayout.isLocationInfo(), "locationInfo"); + // PatternLayout + final PatternLayout patternLayout = (PatternLayout) testLayout(config, "PatternLayout"); + assertNotNull(patternLayout); + assertEquals("%m%n", patternLayout.getConversionPattern(), "conversionPattern"); + // TTCCLayout + final PatternLayout ttccLayout = (PatternLayout) testLayout(config, "TTCCLayout"); + assertNotNull(ttccLayout); + assertEquals( + "%r [%t] %p %c %notEmpty{%ndc }- %m%n", + ttccLayout.getConversionPattern(), "equivalent conversion pattern"); + // TODO: XMLLayout + // final XmlLayout xmlLayout = (XmlLayout) testLayout(config, "XMLLayout"); + // assertNotNull(xmlLayout); + // ConsoleAppender + final ConsoleAppender consoleAppender = config.getAppender("ConsoleAppender"); + assertNotNull(consoleAppender); + assertEquals(Target.SYSTEM_OUT, consoleAppender.getTarget(), "target"); + final boolean follow = getFollowProperty(consoleAppender); + assertFalse(follow, "follow"); + // DailyRollingFileAppender + final RollingFileAppender dailyRollingFileAppender = config.getAppender("DailyRollingFileAppender"); + assertNotNull(dailyRollingFileAppender); + assertEquals( + "target/dailyRollingFileAppender%d{.yyyy-MM-dd}", + dailyRollingFileAppender.getFilePattern(), "equivalent file pattern"); + assertTrue(getAppendProperty(dailyRollingFileAppender), "append"); + assertEquals(8192, dailyRollingFileAppender.getManager().getBufferSize(), "bufferSize"); + assertTrue(dailyRollingFileAppender.getImmediateFlush(), "immediateFlush"); + // FileAppender + final FileAppender fileAppender = config.getAppender("FileAppender"); + assertNotNull(fileAppender); + assertTrue(getAppendProperty(fileAppender), "append"); + assertEquals(8192, fileAppender.getManager().getBufferSize(), "bufferSize"); + assertTrue(fileAppender.getImmediateFlush(), "immediateFlush"); + // RollingFileAppender + final RollingFileAppender rollingFileAppender = config.getAppender("RollingFileAppender"); + assertNotNull(rollingFileAppender); + assertEquals("target/rollingFileAppender.%i", rollingFileAppender.getFilePattern(), "equivalent file pattern"); + final CompositeTriggeringPolicy compositePolicy = + rollingFileAppender.getManager().getTriggeringPolicy(); + assertEquals(1, compositePolicy.getTriggeringPolicies().length); + final SizeBasedTriggeringPolicy sizePolicy = + (SizeBasedTriggeringPolicy) compositePolicy.getTriggeringPolicies()[0]; + assertEquals(10 * 1024 * 1024L, sizePolicy.getMaxFileSize(), "maxFileSize"); + final DefaultRolloverStrategy strategy = + (DefaultRolloverStrategy) rollingFileAppender.getManager().getRolloverStrategy(); + assertEquals(1, strategy.getMaxIndex(), "maxBackupIndex"); + assertTrue(getAppendProperty(rollingFileAppender), "append"); + assertEquals(8192, rollingFileAppender.getManager().getBufferSize(), "bufferSize"); + assertTrue(rollingFileAppender.getImmediateFlush(), "immediateFlush"); + config.start(); + config.stop(); + } + + /** + * Checks a hierarchy of filters. + * + * @param filter A 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( + ((FilterAdapter) filter).getFilter() instanceof FilterWrapper, + "found FilterAdapter of a FilterWrapper"); + count += checkFilters(((FilterAdapter) filter).getFilter()); + } else { + count++; + } + return count; + } + + /** + * Checks a hierarchy of filters. + * + * @param filter A 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( + ((FilterWrapper) filter).getFilter() instanceof FilterAdapter, + "found FilterWrapper of a 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(filter.getNext(), "found chain of Log4j 1.x filters"); + return count; + } + + void testMultipleFilters() throws Exception { + System.setProperty("test.tmpDir", tempDir.getCanonicalPath()); + + try (final 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(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"); + } + } + + 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(); + assertInstanceOf(ThresholdFilter.class, filter); + final 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()); + } + } + + protected void testEnhancedRollingFileAppender(final Configuration configuration) { + Appender appender; + TriggeringPolicy policy; + RolloverStrategy strategy; + DefaultRolloverStrategy defaultRolloverStrategy; + // Time policy with default attributes + appender = configuration.getAppender("DEFAULT_TIME"); + assertInstanceOf(RollingFileAppender.class, appender, "is RollingFileAppender"); + final RollingFileAppender defaultTime = (RollingFileAppender) appender; + assertTrue(defaultTime.getManager().isAppend(), "append"); + assertEquals(8192, defaultTime.getManager().getBufferSize(), "bufferSize"); + assertTrue(defaultTime.getImmediateFlush(), "immediateFlush"); + assertEquals("target/EnhancedRollingFileAppender/defaultTime.log", defaultTime.getFileName(), "fileName"); + assertEquals( + "target/EnhancedRollingFileAppender/defaultTime.%d{yyyy-MM-dd}.log", + defaultTime.getFilePattern(), "filePattern"); + policy = defaultTime.getTriggeringPolicy(); + assertInstanceOf(TimeBasedTriggeringPolicy.class, policy, "is TimeBasedTriggeringPolicy"); + // Size policy with default attributes + appender = configuration.getAppender("DEFAULT_SIZE"); + assertInstanceOf(RollingFileAppender.class, appender, "is RollingFileAppender"); + final RollingFileAppender defaultSize = (RollingFileAppender) appender; + assertTrue(defaultSize.getManager().isAppend(), "append"); + assertEquals(8192, defaultSize.getManager().getBufferSize(), "bufferSize"); + assertTrue(defaultSize.getImmediateFlush(), "immediateFlush"); + assertEquals("target/EnhancedRollingFileAppender/defaultSize.log", defaultSize.getFileName(), "fileName"); + assertEquals( + "target/EnhancedRollingFileAppender/defaultSize.%i.log", defaultSize.getFilePattern(), "filePattern"); + policy = defaultSize.getTriggeringPolicy(); + assertInstanceOf(SizeBasedTriggeringPolicy.class, policy, "is SizeBasedTriggeringPolicy"); + assertEquals(10 * 1024 * 1024L, ((SizeBasedTriggeringPolicy) policy).getMaxFileSize()); + strategy = defaultSize.getManager().getRolloverStrategy(); + assertInstanceOf(DefaultRolloverStrategy.class, strategy, "is DefaultRolloverStrategy"); + defaultRolloverStrategy = (DefaultRolloverStrategy) strategy; + assertEquals(1, defaultRolloverStrategy.getMinIndex()); + assertEquals(7, defaultRolloverStrategy.getMaxIndex()); + // Time policy with custom attributes + appender = configuration.getAppender("TIME"); + assertInstanceOf(RollingFileAppender.class, appender, "is RollingFileAppender"); + final RollingFileAppender time = (RollingFileAppender) appender; + assertFalse(time.getManager().isAppend(), "append"); + assertEquals(1000, time.getManager().getBufferSize(), "bufferSize"); + assertFalse(time.getImmediateFlush(), "immediateFlush"); + assertEquals("target/EnhancedRollingFileAppender/time.log", time.getFileName(), "fileName"); + assertEquals( + "target/EnhancedRollingFileAppender/time.%d{yyyy-MM-dd}.log", time.getFilePattern(), "filePattern"); + policy = time.getTriggeringPolicy(); + assertInstanceOf(TimeBasedTriggeringPolicy.class, policy, "is TimeBasedTriggeringPolicy"); + // Size policy with custom attributes + appender = configuration.getAppender("SIZE"); + assertInstanceOf(RollingFileAppender.class, appender, "is RollingFileAppender"); + final RollingFileAppender size = (RollingFileAppender) appender; + assertFalse(size.getManager().isAppend(), "append"); + assertEquals(1000, size.getManager().getBufferSize(), "bufferSize"); + assertFalse(size.getImmediateFlush(), "immediateFlush"); + assertEquals("target/EnhancedRollingFileAppender/size.log", size.getFileName(), "fileName"); + assertEquals("target/EnhancedRollingFileAppender/size.%i.log", size.getFilePattern(), "filePattern"); + policy = size.getTriggeringPolicy(); + assertInstanceOf(SizeBasedTriggeringPolicy.class, policy, "is SizeBasedTriggeringPolicy"); + assertEquals(10_000_000L, ((SizeBasedTriggeringPolicy) policy).getMaxFileSize()); + strategy = size.getManager().getRolloverStrategy(); + assertInstanceOf(DefaultRolloverStrategy.class, strategy, "is DefaultRolloverStrategy"); + defaultRolloverStrategy = (DefaultRolloverStrategy) strategy; + assertEquals(11, defaultRolloverStrategy.getMinIndex()); + assertEquals(20, defaultRolloverStrategy.getMaxIndex()); + } + + protected void testLevelRangeFilter() throws Exception { + try (final LoggerContext ctx = configure("config-1.2/log4j-LevelRangeFilter")) { + final Configuration config = ctx.getConfiguration(); + final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class); + // List appender + final Appender appender = config.getAppender("LIST"); + assertNotNull(appender); + final ListAppender legacyAppender = (ListAppender) ((Adapter) appender).getAppender(); + // deny + logger.trace("TRACE"); + assertEquals(0, legacyAppender.getEvents().size()); + // deny + logger.debug("DEBUG"); + assertEquals(0, legacyAppender.getEvents().size()); + // accept + logger.info("INFO"); + assertEquals(1, legacyAppender.getEvents().size()); + // accept + logger.warn("WARN"); + assertEquals(2, legacyAppender.getEvents().size()); + // accept + logger.error("ERROR"); + assertEquals(3, legacyAppender.getEvents().size()); + // deny + logger.fatal("FATAL"); + assertEquals(3, 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..b74c3365ae7 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.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.log4j.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.net.URI; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +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.LoggerContext; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Test configuration from XML. + */ +@UsingStatusListener +class AsyncAppenderTest { + + private static long DEFAULT_TIMEOUT_MS = 500; + + static Stream testAsyncAppender() { + return Stream.of("/log4j1-async.xml", "/log4j1-async.properties") + .map(config -> assertDoesNotThrow(() -> { + final URI uri = AsyncAppenderTest.class.getResource(config).toURI(); + return Paths.get(uri).toString(); + })); + } + + @ParameterizedTest + @MethodSource + void testAsyncAppender(final String configLocation) throws Exception { + try (final LoggerContext loggerContext = TestConfigurator.configure(configLocation)) { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + final AppenderAdapter.Adapter adapter = + loggerContext.getConfiguration().getAppender("list"); + assertThat(adapter).isNotNull(); + final ListAppender appender = (ListAppender) adapter.getAppender(); + final List messages = appender.getMessages(1, DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + assertThat(messages).hasSize(1); + } + } +} 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..be551f3d8cd --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.spi.LoggerContext; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from XML. + */ +class AutoConfigTest { + + @BeforeAll + static void beforeAll() { + System.setProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL, "true"); + } + + @Test + void testListAppender() { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + final LoggerContext loggerContext = org.apache.logging.log4j.LogManager.getContext(false); + final Configuration configuration = + ((org.apache.logging.log4j.core.LoggerContext) loggerContext).getConfiguration(); + final 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"); + final List events = eventAppender.getEvents(); + assertTrue(events != null && !events.isEmpty(), "No events"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "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..d8b76f9954d 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,39 +1,40 @@ -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 + * contributor license agreements. See the NOTICE file distributed 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.config; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class Log4j1ConfigurationConverterHadoopTest extends AbstractLog4j1ConfigurationConverterTest { - @Parameterized.Parameters(name = "{0}") public static List data() throws IOException { return getPaths("src/test/resources/config-1.2/hadoop"); } - public Log4j1ConfigurationConverterHadoopTest(final Path path) { - super(path); + public Log4j1ConfigurationConverterHadoopTest() { + super(); } + @ParameterizedTest + @MethodSource("data") + public void test(Path path) throws Exception { + super.test(path); + } } 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..29da3c77246 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,39 +1,40 @@ -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 + * contributor license agreements. See the NOTICE file distributed 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.config; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class Log4j1ConfigurationConverterSparkTest extends AbstractLog4j1ConfigurationConverterTest { - @Parameterized.Parameters(name = "{0}") public static List data() throws IOException { return getPaths("src/test/resources/config-1.2/spark"); } - public Log4j1ConfigurationConverterSparkTest(final Path path) { - super(path); + public Log4j1ConfigurationConverterSparkTest() { + super(); } + @ParameterizedTest + @MethodSource("data") + public void test(Path path) throws Exception { + super.test(path); + } } 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..6636544668f 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 @@ -1,63 +1,65 @@ /* * 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.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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.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; +import org.junit.jupiter.api.Test; + +class Log4j1ConfigurationFactoryTest extends AbstractLog4j1ConfigurationTest { -public class Log4j1ConfigurationFactoryTest { + 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 +70,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()); - } - - @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()); - } - - @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 testConsoleSimpleLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-SimpleLayout.properties"); - assertEquals("%level - %m%n", layout.getConversionPattern()); - } - - @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()); - } - - @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 testFileSimpleLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testFile("config-1.2/log4j-file-SimpleLayout.properties"); - assertEquals("%level - %m%n", layout.getConversionPattern()); - } - - @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); - } - - @Test - public void testRollingFileAppender() throws Exception { - testRollingFileAppender("config-1.2/log4j-RollingFileAppender.properties", "RFA", "target/hadoop.log.%i"); - } - - @Test - public void testDailyRollingFileAppender() throws Exception { - testDailyRollingFileAppender("config-1.2/log4j-DailyRollingFileAppender.properties", "DRFA", "target/hadoop.log%d{.yyyy-MM-dd}"); - } - - @Test - public void testRollingFileAppenderWithProperties() throws Exception { - testRollingFileAppender("config-1.2/log4j-RollingFileAppender-with-props.properties", "RFA", "target/hadoop.log.%i"); - } - - @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); + @Override + @Test + void testConsoleEnhancedPatternLayout() throws Exception { + super.testConsoleEnhancedPatternLayout(); + } + + @Override + @Test + void testConsoleHtmlLayout() throws Exception { + super.testConsoleHtmlLayout(); + } + + @Test + void testConsolePatternLayout() throws Exception { + super.testConsolePatternLayout(); + } + + @Override + @Test + void testConsoleSimpleLayout() throws Exception { + super.testConsoleSimpleLayout(); + } + + @Override + @Test + void testConsoleTtccLayout() throws Exception { + super.testConsoleTtccLayout(); + } + + @Test + void testConsoleXmlLayout() throws Exception { + final Log4j1XmlLayout layout = (Log4j1XmlLayout) testConsole("config-1.2/log4j-console-XmlLayout"); + assertTrue(layout.isLocationInfo()); + assertFalse(layout.isProperties()); + } + + @Override + @Test + void testFileSimpleLayout() throws Exception { + super.testFileSimpleLayout(); + } + + @Override + @Test + void testNullAppender() throws Exception { + super.testNullAppender(); + } + + @Override + @Test + void testRollingFileAppender() throws Exception { + super.testRollingFileAppender(); + } + + @Override + @Test + void testDailyRollingFileAppender() throws Exception { + super.testDailyRollingFileAppender(); + } + + @Test + void testRollingFileAppenderWithProperties() throws Exception { + super.testRollingFileAppenderWithProperties(); + } + + @Override + @Test + void testSystemProperties1() throws Exception { + super.testSystemProperties1(); + } + + @Override + @Test + void testSystemProperties2() throws Exception { + super.testSystemProperties2(); + } + + @Override + @Test + void testConsoleCapitalization() throws Exception { + super.testConsoleCapitalization(); + } + + @Override + @Test + void testDefaultValues() throws Exception { + super.testDefaultValues(); + } + + @Test + void testUntrimmedValues() throws Exception { 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(); - } + 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"); + assertInstanceOf(ConsoleAppender.class, appender); + final Layout layout = appender.getLayout(); + assertInstanceOf(PatternLayout.class, layout); + assertEquals("%v1Level - %m%n", ((PatternLayout) layout).getConversionPattern()); + // No filter support + config.start(); + config.stop(); + } catch (NoClassDefFoundError e) { + fail(e.getMessage()); } - } - - @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); - } - - 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(); - } - - 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(); - } + } + @Test + 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(); + assertInstanceOf(ThresholdFilter.class, filter); + final 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..bcae05d6bee --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.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.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 java.util.HashMap; +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.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.config.ConfigurationFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test RewriteAppender + */ +class MapRewriteAppenderTest { + + @BeforeAll + static void beforeAll() { + System.setProperty( + ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-mapRewrite.xml"); + } + + @AfterEach + void after() { + ThreadContext.clearMap(); + } + + @Test + void testRewrite() { + final Logger logger = LogManager.getLogger("test"); + final Map map = new HashMap<>(); + map.put("message", "This is a test"); + map.put("hello", "world"); + logger.debug(map); + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + final Configuration configuration = context.getConfiguration(); + final 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"); + final List events = eventAppender.getEvents(); + assertTrue(events != null && !events.isEmpty(), "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..d787acd1983 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/NeutralFilterFixture.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.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(final 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..7b14885786d --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationFactoryTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from Properties. + */ +class PropertiesConfigurationFactoryTest { + + @BeforeAll + static void beforeAll() { + System.setProperty( + ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, + "target/test-classes/log4j1-file-1.properties"); + } + + @Test + void testProperties() { + 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"); + } +} 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..fb8ed815671 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.assertInstanceOf; +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.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.config.plugins.util.PluginManager; +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; + +/** + * Test configuration from Properties. + */ +class PropertiesConfigurationTest extends AbstractLog4j1ConfigurationTest { + + private static final String TEST_KEY = "log4j.test.tmpdir"; + private static final String SUFFIX = ".properties"; + + @Override + Configuration getConfiguration(final String configResourcePrefix) throws IOException { + final String configResource = configResourcePrefix + SUFFIX; + final InputStream inputStream = getResourceAsStream(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 + void testConfigureNullPointerException() throws Exception { + try (final 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 + void testConsoleAppenderFilter() throws Exception { + try (final 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); + assertInstanceOf(NeutralFilterFixture.class, filter.getFilter()); + } + } + + @Test + void testCustomAppenderFilter() throws Exception { + try (final 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); + assertInstanceOf(NeutralFilterFixture.class, filter.getFilter()); + } + } + + @Test + void testConsoleAppenderLevelRangeFilter() throws Exception { + PluginManager.addPackage("org.apache.log4j.builders.filter"); + try (final 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 filter1 = (LevelRangeFilter) filters[0]; + // XXX: LOG4J2-2315 + assertEquals(Level.OFF, filter1.getMinLevel()); + assertEquals(Level.ALL, filter1.getMaxLevel()); + final LevelRangeFilter filter2 = (LevelRangeFilter) filters[1]; + assertEquals(Level.ERROR, filter2.getMinLevel()); + assertEquals(Level.INFO, filter2.getMaxLevel()); + final LevelRangeFilter filter3 = (LevelRangeFilter) filters[2]; + assertEquals(Level.OFF, filter3.getMinLevel()); + assertEquals(Level.ALL, filter3.getMaxLevel()); + + final ListAppender legacyAppender = (ListAppender) ((AppenderAdapter.Adapter) appender).getAppender(); + final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class); + + // deny + logger.trace("TRACE"); + assertEquals(0, legacyAppender.getEvents().size()); + // deny + logger.debug("DEBUG"); + assertEquals(0, legacyAppender.getEvents().size()); + // accept + logger.info("INFO"); + assertEquals(1, legacyAppender.getEvents().size()); + // accept + logger.warn("WARN"); + assertEquals(2, legacyAppender.getEvents().size()); + // accept + logger.error("ERROR"); + assertEquals(3, legacyAppender.getEvents().size()); + // deny + logger.fatal("FATAL"); + assertEquals(3, legacyAppender.getEvents().size()); + } + } + + @Test + void testConfigureAppenderDoesNotExist() throws Exception { + // Verify that we tolerate a logger which specifies an appender that does not exist. + try (final LoggerContext loggerContext = + TestConfigurator.configure("target/test-classes/LOG4J2-3407.properties")) { + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + } + } + + @Test + void testListAppender() throws Exception { + try (final 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.isEmpty(), "No events"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages"); + } + } + + @Test + void testProperties() throws Exception { + try (final 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 + void testSystemProperties() throws Exception { + final String testPathLocation = "target"; + System.setProperty(TEST_KEY, testPathLocation); + try (final 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); + assertInstanceOf(FileAppender.class, appender, 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() throws Exception { + super.testMultipleFilters(); + } + + @Test + 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"); + assertInstanceOf(ConsoleAppender.class, appender); + final Layout layout = appender.getLayout(); + assertInstanceOf(PatternLayout.class, layout); + assertEquals("%v1Level - %m%n", ((PatternLayout) layout).getConversionPattern()); + final Filter filter = ((Filterable) appender).getFilter(); + assertInstanceOf(DenyAllFilter.class, filter); + config.start(); + config.stop(); + } catch (NoClassDefFoundError e) { + fail(e.getMessage()); + } + } + + @Override + @Test + public void testGlobalThreshold() throws Exception { + super.testGlobalThreshold(); + } + + @Test + void testEnhancedRollingFileAppender() throws Exception { + try (final LoggerContext ctx = configure("config-1.2/log4j-EnhancedRollingFileAppender")) { + final Configuration configuration = ctx.getConfiguration(); + assertNotNull(configuration); + testEnhancedRollingFileAppender(configuration); + } + } + + @Override + @Test + public void testLevelRangeFilter() throws Exception { + super.testLevelRangeFilter(); + } +} 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..dbdca87084c --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesReconfigurationTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +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.Test; + +/** + * Test reconfiguring with an XML configuration. + */ +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, + final FailableConsumer configurator) + throws IOException { + final File file = new File(configPath); + assertTrue(file.exists(), "No Config file"); + try (final 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 (final 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 + 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 + 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 + 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 + 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 (final 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(); + assertNotEquals(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..ea740d93d4f --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from Properties. + */ +class PropertiesRollingWithPropertiesTest { + + private static final String TEST_DIR = "target/" + PropertiesRollingWithPropertiesTest.class.getSimpleName(); + + @BeforeAll + static void setupSystemProperties() { + // Set system properties as a replacement for SystemPropertyTestRule + System.setProperty("test.directory", TEST_DIR); + System.setProperty("log4j.configuration", "target/test-classes/log4j1-rolling-properties.properties"); + } + + @Test + void testProperties() throws Exception { + final Path path = Paths.get(TEST_DIR, "somefile.log"); + Files.deleteIfExists(path); + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + assertTrue(Files.exists(path), "Log file was not created"); + assertTrue(Files.size(path) > 0, "Log file is empty"); + } +} 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..86af9aea741 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.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.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 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.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.config.ConfigurationFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test RewriteAppender + */ +class RewriteAppenderTest { + + @BeforeAll + static void beforeAll() { + System.setProperty( + ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-rewrite.xml"); + } + + @AfterEach + void after() { + ThreadContext.clearMap(); + } + + @Test + void testRewrite() { + final Logger logger = LogManager.getLogger("test"); + ThreadContext.put("key1", "This is a test"); + ThreadContext.put("hello", "world"); + final long logTime = System.currentTimeMillis(); + logger.debug("Say hello"); + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + final Configuration configuration = context.getConfiguration(); + final 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"); + final List events = eventAppender.getEvents(); + assertTrue(events != null && !events.isEmpty(), "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..2294c556d18 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SocketAppenderConfigurationTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.jupiter.api.Test; + +/** + * Tests configuring a Syslog appender. + */ +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 + void testProperties() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-socket.properties"); + } + + @Test + void testPropertiesXmlLayout() throws Exception { + final SocketAppender socketAppender = + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-socket-xml-layout.properties"); + assertInstanceOf(Log4j1XmlLayout.class, socketAppender.getLayout()); + } + + @Test + 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..7ad3774deab --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.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.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(final LoggingEvent event) { + final 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..1313cee2c44 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderConfigurationTest.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.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 java.io.IOException; +import java.net.ServerSocket; +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.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.WritesSystemProperty; + +/** + * Tests configuring a Syslog appender. + */ +@UsingStatusListener +@WritesSystemProperty +class SyslogAppenderConfigurationTest { + + private static ServerSocket tcpSocket; + + @BeforeAll + static void setup() throws IOException { + // TCP appenders log an error if there is no server socket + tcpSocket = new ServerSocket(0); + System.setProperty("syslog.port", Integer.toString(tcpSocket.getLocalPort())); + } + + @AfterAll + static void cleanup() throws IOException { + System.clearProperty("syslog.port"); + tcpSocket.close(); + } + + 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(tcpSocket.getLocalPort(), 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 + void testPropertiesProtocolDefault() throws Exception { + checkProtocolPropertiesConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-default.properties"); + } + + @Test + void testPropertiesProtocolTcp() throws Exception { + checkProtocolPropertiesConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-tcp.properties"); + } + + @Test + void testPropertiesProtocolUdp() throws Exception { + checkProtocolPropertiesConfig(Protocol.UDP, "target/test-classes/log4j1-syslog-protocol-udp.properties"); + } + + @Test + void testXmlProtocolDefault() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-syslog.xml"); + } + + @Test + void testXmlProtocolTcp() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-tcp.xml"); + } + + @Test + 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..720577bc86e --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderTest.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.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +import java.io.IOException; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class SyslogAppenderTest { + + private static MockSyslogServer syslogServer; + + @BeforeAll + static void beforeAll() throws IOException { + initTCPTestEnvironment(null); + System.setProperty("syslog.port", Integer.toString(syslogServer.getLocalPort())); + System.setProperty( + ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-syslog.xml"); + } + + @AfterAll + static void afterAll() { + System.clearProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY); + syslogServer.shutdown(); + } + + @Test + void sendMessage() throws Exception { + final 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.isEmpty()) { + break; + } + } + assertThat(messages, hasSize(1)); + } + + protected static void initTCPTestEnvironment(final String messageFormat) throws IOException { + syslogServer = MockSyslogServerFactory.createTCPSyslogServer(); + 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..8ee8b2b9f70 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/TestConfigurator.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.config; + +import static org.junit.jupiter.api.Assertions.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(configuration, "No configuration created"); + 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..e28121da899 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from XML. + */ +class XmlConfigurationFactoryTest { + + @BeforeAll + static void beforeAll() { + System.setProperty( + ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-file.xml"); + } + + @Test + void testXML() { + 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"); + } +} 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..95a260ed862 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +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.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.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from XML. + */ +class XmlConfigurationTest extends AbstractLog4j1ConfigurationTest { + + private static final String SUFFIX = ".xml"; + + @Override + Configuration getConfiguration(final String configResourcePrefix) throws IOException { + final String configResource = configResourcePrefix + SUFFIX; + final InputStream inputStream = getResourceAsStream(configResource); + final ConfigurationSource source = new ConfigurationSource(inputStream); + final LoggerContext context = LoggerContext.getContext(false); + final Configuration configuration = new XmlConfigurationFactory().getConfiguration(context, source); + assertNotNull(configuration, "No configuration created"); + configuration.initialize(); + return configuration; + } + + @Test + void testListAppender() throws Exception { + final LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-list.xml"); + 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.isEmpty(), "No events"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages"); + } + + @Test + void testXML() throws Exception { + TestConfigurator.configure("target/test-classes/log4j1-file.xml"); + 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"); + } + + @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() throws Exception { + super.testMultipleFilters(); + } + + @Override + @Test + public void testGlobalThreshold() throws Exception { + super.testGlobalThreshold(); + } + + @Test + void testEnhancedRollingFileAppender() throws Exception { + try (final LoggerContext ctx = configure("config-1.2/log4j-EnhancedRollingFileAppender")) { + final Configuration configuration = ctx.getConfiguration(); + assertNotNull(configuration); + testEnhancedRollingFileAppender(configuration); + // Only supported through XML configuration + final Appender appender = configuration.getAppender("MIXED"); + assertInstanceOf(RollingFileAppender.class, appender, "is RollingFileAppender"); + final TriggeringPolicy policy = ((RollingFileAppender) appender).getTriggeringPolicy(); + assertInstanceOf(CompositeTriggeringPolicy.class, policy, "is CompositeTriggeringPolicy"); + final TriggeringPolicy[] policies = ((CompositeTriggeringPolicy) policy).getTriggeringPolicies(); + assertEquals(2, policies.length); + assertInstanceOf(TimeBasedTriggeringPolicy.class, policies[0], "is TimeBasedTriggeringPolicy"); + assertInstanceOf(SizeBasedTriggeringPolicy.class, policies[1], "is SizeBasedTriggeringPolicy"); + } + } + + @Override + @Test + public void testLevelRangeFilter() throws Exception { + super.testLevelRangeFilter(); + } +} 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..ee9dbfcdb9a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlReconfigurationTest.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.config; + +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; + +import java.io.File; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +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.Reconfigurable; +import org.junit.jupiter.api.Test; + +/** + * Test reconfiguring with an XML configuration. + */ +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 + void testReconfiguration() throws Exception { + System.setProperty(Log4j1Configuration.MONITOR_INTERVAL, "1"); + final File file = new File(CONFIG); + assertNotNull(file, "No Config file"); + final long configMillis = file.lastModified(); + assertTrue(file.setLastModified(configMillis - FIVE_MINUTES), "Unable to modified file time"); + final 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 (InterruptedException ie) { + fail("Reconfiguration interupted"); + } + final Configuration updated = context.getConfiguration(); + assertNotEquals(original, updated, "Configurations are the same"); + } + + private class TestListener implements ConfigurationListener { + + public synchronized void onChange(final Reconfigurable reconfigurable) { + toggle.countDown(); + } + } +} 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..0847a3cc93e --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Test configuration from Properties. + */ +class XmlRollingWithPropertiesTest { + + private static final String TEST_DIR = "target/" + XmlRollingWithPropertiesTest.class.getSimpleName(); + + @BeforeAll + static void setupSystemProperties() { + System.setProperty("test.directory", TEST_DIR); + System.setProperty("log4j.configuration", "target/test-classes/log4j1-rolling-properties.xml"); + } + + @Test + void testProperties() throws Exception { + // ${test.directory}/logs/etl.log + final Path path = Paths.get(TEST_DIR, "logs/etl.log"); + Files.deleteIfExists(path); + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + assertTrue(Files.exists(path), "Log file was not created " + path); + assertTrue(Files.size(path) > 0, "Log file is empty " + path); + } +} 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..d45b21448e0 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/BoundedFIFOTestCase.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +/** + * 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 Math.min(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 = Math.min(size, j); + j--; + for (k = 0; k <= j; k++) { + // System.out.println("max="+max+", j="+j+", k="+k); + assertEquals(bf.length(), Math.max(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(1, bf.length()); + bf.put(e[2]); + assertEquals(2, bf.length()); + bf.put(e[3]); + assertEquals(3, bf.length()); + assertEquals(bf.get(), e[1]); + assertEquals(2, bf.length()); + assertEquals(bf.get(), e[2]); + assertEquals(1, bf.length()); + assertEquals(bf.get(), e[3]); + assertEquals(0, bf.length()); + assertNull(bf.get()); + assertEquals(0, bf.length()); + } + + /** + * 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..864e400e30a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/CyclicBufferTestCase.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 junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +/** + * 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 = Math.min(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 = Math.min(newSize, numberOfAdds); + len = Math.min(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(size, cb.getMaxSize()); + + cb.add(e[0]); + assertEquals(1, cb.length()); + assertEquals(cb.get(), e[0]); + assertEquals(0, cb.length()); + assertNull(cb.get()); + assertEquals(0, cb.length()); + + cb = new CyclicBuffer(size); + cb.add(e[0]); + cb.add(e[1]); + assertEquals(2, cb.length()); + assertEquals(cb.get(), e[0]); + assertEquals(1, cb.length()); + assertEquals(cb.get(), e[1]); + assertEquals(0, cb.length()); + assertNull(cb.get()); + assertEquals(0, cb.length()); + } + + /** + * 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..8ec4d21d7a4 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/DateLayoutTest.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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() { + final String[] options = ((DateLayout) createLayout()).getOptionStrings(); + assertEquals(2, options.length); + } + + /** + * Tests setting DateFormat through setOption method. + * + * @deprecated since setOption is deprecated. + */ + public void testSetOptionDateFormat() { + final 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() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setOption("tImezOne", "+05:00"); + assertEquals("+05:00", layout.getTimeZone()); + } + + /** + * Tests setDateFormat. + */ + public void testSetDateFormat() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("ABSOLUTE"); + assertEquals("ABSOLUTE", layout.getDateFormat()); + } + + /** + * Tests setTimeZone. + */ + public void testSetTimeZone() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setTimeZone("+05:00"); + assertEquals("+05:00", layout.getTimeZone()); + } + + /** + * Tests 2 parameter setDateFormat with null. + */ + public void testSetDateFormatNull() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat((String) null, null); + } + + /** + * Tests 2 parameter setDateFormat with "NULL". + */ + public void testSetDateFormatNullString() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("NuLL", null); + } + + /** + * Tests 2 parameter setDateFormat with "RELATIVE". + */ + public void testSetDateFormatRelative() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("rElatIve", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "ABSOLUTE". + */ + public void testSetDateFormatAbsolute() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("aBsolUte", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "DATETIME". + */ + public void testSetDateFormatDateTime() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("dAte", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "ISO8601". + */ + public void testSetDateFormatISO8601() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("iSo8601", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "HH:mm:ss". + */ + public void testSetDateFormatSimple() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("HH:mm:ss", TimeZone.getDefault()); + } + + /** + * Tests activateOptions. + */ + public void testActivateOptions() { + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("HH:mm:ss"); + layout.setTimeZone("+05:00"); + layout.activateOptions(); + } + + /** + * Tests setDateFormat(DateFormat, TimeZone). + */ + public void testSetDateFormatWithFormat() { + final DateFormat format = new SimpleDateFormat("HH:mm"); + final DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat(format, TimeZone.getDefault()); + } + + /** + * Tests IS08601DateFormat class. + * + * @deprecated since ISO8601DateFormat is deprecated + */ + public void testISO8601Format() { + final DateFormat format = new ISO8601DateFormat(); + final Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(1970, 0, 1, 0, 0, 0); + final 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() { + final DateFormat format = new DateTimeDateFormat(); + final Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(1970, 0, 1, 0, 0, 0); + final String actual = format.format(calendar.getTime()); + final SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm:ss,SSS"); + final 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..f852023fe59 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/LogLogTest.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.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..2156540c111 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java @@ -0,0 +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.log4j.helpers; + +import static org.apache.log4j.helpers.OptionConverter.toLog4j1Level; +import static org.apache.log4j.helpers.OptionConverter.toLog4j2Level; +import static org.assertj.core.api.Assertions.assertThat; +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 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; + +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. + */ + @ParameterizedTest + @MethodSource("standardLevels") + void testStandardLevelConversion(final Level log4j1Level, final org.apache.logging.log4j.Level log4j2Level) { + assertThat(log4j2Level).isSameAs(OptionConverter.convertLevel(log4j1Level)); + assertThat(log4j1Level).isSameAs(OptionConverter.convertLevel(log4j2Level)); + assertThat(OptionConverter.toLevel(org.apache.logging.log4j.Level.class.getName(), log4j2Level.name(), null)) + .isSameAs(OptionConverter.convertLevel(log4j2Level)); + } + + /** + * Test if the conversion works at an integer level. + */ + @ParameterizedTest + @MethodSource("standardLevels") + 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 + 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 + 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 + 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/UtilLoggingLevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java new file mode 100644 index 00000000000..2e41de6b142 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.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.helpers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.apache.log4j.Level; +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; + +/** + * Unit tests for UtilLoggingLevel. + */ +public class UtilLoggingLevelTest { + + /** + * Test toLevel("fiNeSt"). + */ + @Test + 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") + void testOptionConverterToLevel(final String name, final UtilLoggingLevel level) { + assertEquals(level, OptionConverter.toLevel(name, Level.ALL), "get v1 level by name"); + // Comparison of Log4j 2.x levels + assertEquals(level.getVersion2Level(), org.apache.logging.log4j.Level.getLevel(name), "get v2 level by name"); + // Test convertLevel + assertEquals(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..e684c3a5b94 --- /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; + +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") + void testSimpleLayout( + final String expected, final Facility facility, final boolean header, final 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..1af12167a60 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 @@ -1,34 +1,36 @@ /* * 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.layout; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; 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.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; import org.apache.logging.log4j.util.StringMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class Log4j1XmlLayoutTest { +@UsingThreadContextStack +class Log4j1XmlLayoutTest { @Test - public void testWithoutThrown() { + void testWithoutThrown() { final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(false, true); final Log4jLogEvent event = Log4jLogEvent.newBuilder() @@ -40,16 +42,16 @@ public void testWithoutThrown() { final String result = layout.toSerializable(event); - final String expected = - "\r\n" + - "\r\n" + - "\r\n\r\n"; + final String expected = "\r\n" + + "\r\n" + + "\r\n\r\n"; assertEquals(expected, result); } @Test - public void testWithPropertiesAndLocationInfo() { + void testWithPropertiesAndLocationInfo() { final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(true, true); final StringMap contextMap = ContextDataFactory.createContextData(2); @@ -67,17 +69,16 @@ public void testWithPropertiesAndLocationInfo() { final String result = layout.toSerializable(event); - final String expected = - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n"+ - "\r\n\r\n"; + final String expected = "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n\r\n"; assertEquals(expected, result); } - } 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..291224674ea --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/FormattingInfoTest.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.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() { + final FormattingInfo field = new FormattingInfo(true, 3, 6); + assertNotNull(field); + assertEquals(3, field.getMinLength()); + assertEquals(6, field.getMaxLength()); + assertTrue(field.isLeftAligned()); + } + + /** + * Check that getDefault does not return null. + * + */ + public void testGetDefault() { + final FormattingInfo field = FormattingInfo.getDefault(); + assertNotNull(field); + assertEquals(0, field.getMinLength()); + assertEquals(Integer.MAX_VALUE, field.getMaxLength()); + assertFalse(field.isLeftAligned()); + } + + /** + * Add padding to left since field is not minimum width. + */ + public void testPadLeft() { + final StringBuffer buf = new StringBuffer("foobar"); + final 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() { + final StringBuffer buf = new StringBuffer("foobar"); + final FormattingInfo field = new FormattingInfo(true, 5, 10); + field.format(2, buf); + assertEquals("foobar ", buf.toString()); + } + + /** + * Field exceeds maximum width + */ + public void testTruncate() { + final StringBuffer buf = new StringBuffer("foobar"); + final 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..d6bcbb50fe2 --- /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; + +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") + 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/Log4j1MdcPatternConverterTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.java index c1d5b83254b..7b791c1442d 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.java @@ -1,22 +1,22 @@ /* * 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.pattern; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; @@ -24,19 +24,19 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.StringMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class Log4j1MdcPatternConverterTest { +class Log4j1MdcPatternConverterTest { @Test - public void testConverter0() { + void testConverter0() { final StringMap contextMap = ContextDataFactory.createContextData(0); final String expected = "{}"; test(contextMap, expected, null); } @Test - public void testConverter1() { + void testConverter1() { final StringMap contextMap = ContextDataFactory.createContextData(1); contextMap.putValue("key1", "value1"); final String expected = "{{key1,value1}}"; @@ -44,7 +44,7 @@ public void testConverter1() { } @Test - public void testConverter2() { + void testConverter2() { final StringMap contextMap = ContextDataFactory.createContextData(2); contextMap.putValue("key1", "value1"); contextMap.putValue("key2", "value2"); @@ -53,7 +53,7 @@ public void testConverter2() { } @Test - public void testConverterWithKey() { + void testConverterWithKey() { final StringMap contextMap = ContextDataFactory.createContextData(2); contextMap.putValue("key1", "value1"); contextMap.putValue("key2", "value2"); @@ -73,6 +73,4 @@ private void test(final StringMap contextMap, final String expected, final Strin converter.format(event, sb); assertEquals(expected, sb.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..a0b3da20d40 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 @@ -1,57 +1,54 @@ /* * 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.pattern; +import static org.junit.jupiter.api.Assertions.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.message.SimpleMessage; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class Log4j1NdcPatternConverterTest { +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; +import org.junit.jupiter.api.Test; - @Rule - public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule(); +@UsingThreadContextStack +class Log4j1NdcPatternConverterTest { @Test - public void testEmpty() { + void testEmpty() { testConverter(""); } @Test - public void test1() { + void test1() { ThreadContext.push("foo"); testConverter("foo"); } @Test - public void test2() { + void test2() { ThreadContext.push("foo"); ThreadContext.push("bar"); testConverter("foo bar"); } @Test - public void test3() { + void test3() { ThreadContext.push("foo"); ThreadContext.push("bar"); ThreadContext.push("baz"); @@ -69,6 +66,4 @@ private void testConverter(final String expected) { converter.format(event, sb); assertEquals(expected, sb.toString()); } - } - 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..06f2e9698c2 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/NameAbbreviatorTest.java @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator(" "); + final NameAbbreviator defaultAbbrev = NameAbbreviator.getDefaultAbbreviator(); + assertSame(abbrev, defaultAbbrev); + } + + /** + * Check that blanks are trimmed in evaluating abbreviation pattern. + */ + public void testBlankOne() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator(" 1 "); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getDefaultAbbreviator(); + assertNotNull(abbrev); + } + + /** + * Check that getAbbreviator("-1").abbreviate() drops first name element. + * + */ + public void testMinusOne() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("-1"); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1.*.2"); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1"); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1."); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1~."); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("2"); + final 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() { + final NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("0"); + final StringBuffer buf = new StringBuffer("DEBUG - "); + final 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..0679424cc63 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/LocationInfoTest.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.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"; + final 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() { + final 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..bd8549c0a2d --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/ThrowableInformationTest.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.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() { + final ThrowableInformation ti = new ThrowableInformation(new OverriddenThrowable()); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new NotOverriddenThrowable()); + final String[] rep = ti.getThrowableStrRep(); + assertEquals(7, rep.length); + final StringBuffer buf = new StringBuffer(String.valueOf(true)); + buf.append('a'); + buf.append(1); + buf.append(2L); + buf.append(Float.MAX_VALUE); + buf.append(Double.MIN_VALUE); + buf.append(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() { + final ThrowableInformation ti = new ThrowableInformation(new NullThrowable()); + final String[] rep = ti.getThrowableStrRep(); + assertEquals(2, rep.length); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new EmptyThrowable()); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\n")); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\r")); + final String[] rep = ti.getThrowableStrRep(); + assertEquals(1, rep.length); + assertEquals("", rep[0]); + } + + /** + * Test parsing of line breaks. + */ + public void testParsing() { + final ThrowableInformation ti = + new ThrowableInformation(new StringThrowable("Line1\rLine2\nLine3\r\nLine4\n\rLine6")); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\n ")); + final 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() { + final Throwable t = new StringThrowable("Hello, World"); + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new StringThrowable("Hello, World")); + final String[] rep = ti.getThrowableStrRep(); + assertEquals("Hello, World", rep[0]); + rep[0] = "Bonjour, Monde"; + final 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() { + final ThrowableInformation ti = new ThrowableInformation(new NastyThrowable()); + final String[] rep = ti.getThrowableStrRep(); + assertEquals("NastyException", rep[0]); + } +} 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..e58fa6436e2 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 @@ -2,7 +2,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 * @@ -14,11 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.log4j.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -27,68 +26,23 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; - import org.apache.commons.io.FileUtils; - /** * Utiities for serialization tests. */ -public class SerializationTestHelper { - /** - * Private constructor. - */ - private SerializationTestHelper() { - } - +public final class SerializationTestHelper { /** - * Creates a clone by serializing object and - * deserializing byte stream. + * Checks the serialization of an object against an file containing the expected serialization. * - * @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. - * - * @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 { + 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 +54,14 @@ 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 { + 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,54 @@ 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(expected[i], actual[i], "Difference at offset " + 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/xml/DOMTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/DOMTestCase.java new file mode 100644 index 00000000000..695d32179ff --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/DOMTestCase.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +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.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.log4j.Appender; +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.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@SetTestProperty(key = "log4j1.compatibility", value = "true") +class DOMTestCase { + + Logger root; + + Logger logger; + + @BeforeEach + void setUp() { + root = Logger.getRootLogger(); + logger = Logger.getLogger(DOMTestCase.class); + } + + @AfterEach + void tearDown() { + root.getLoggerRepository().resetConfiguration(); + } + + /** + * Test checks that configureAndWatch does initial configuration, see bug 33502. + * + * @throws Exception if IO error. + */ + @Test + void testConfigureAndWatch() throws Exception { + final URL url = requireNonNull(DOMTestCase.class.getResource("/DOMTestCase/DOMTestCase1.xml")); + DOMConfigurator.configureAndWatch(Paths.get(url.toURI()).toString()); + assertNotNull(Logger.getRootLogger().getAppender("A1")); + } + + /** + * Test for bug 47465. configure(URL) did not close opened JarURLConnection. + * + * @throws IOException if IOException creating properties jar. + */ + @Test + void testJarURL() throws Exception { + final URL url = requireNonNull(DOMTestCase.class.getResource("/DOMTestCase/defaultInit.xml")); + final File input = Paths.get(url.toURI()).toFile(); + System.out.println(input.getAbsolutePath()); + final File configJar = new File("target/output/xml.jar"); + final File dir = new File("target/output"); + Files.createDirectories(dir.toPath()); + try (final InputStream inputStream = Files.newInputStream(input.toPath()); + 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.toURI() + "!/log4j.xml"); + DOMConfigurator.configure(urlInJar); + assertTrue(configJar.delete()); + assertFalse(configJar.exists()); + } + + /** + * This test checks that the subst method of an extending class is checked when evaluating parameters. See bug 43325. + * + */ + @Test + void testOverrideSubst() { + final DOMConfigurator configurator = new DOMConfigurator(); + configurator.doConfigure( + DOMTestCase.class.getResource("/DOMTestCase/DOMTestCase1.xml"), LogManager.getLoggerRepository()); + final String name = "A1"; + final Appender appender = Logger.getRootLogger().getAppender(name); + assertNotNull(appender, name); + final AppenderWrapper wrapper = (AppenderWrapper) appender; + assertNotNull(wrapper, name); + final org.apache.logging.log4j.core.appender.FileAppender a1 = + (org.apache.logging.log4j.core.appender.FileAppender) wrapper.getAppender(); + assertNotNull(a1, wrapper.toString()); + final String file = a1.getFileName(); + assertNotNull(file, a1.toString()); + } + + /** + * Tests that reset="true" on log4j:configuration element resets repository before configuration. + * + */ + @Test + void testReset() { + final VectorAppender appender = new VectorAppender(); + appender.setName("V1"); + Logger.getRootLogger().addAppender(appender); + DOMConfigurator.configure(DOMTestCase.class.getResource("/DOMTestCase/testReset.xml")); + assertNull(Logger.getRootLogger().getAppender("V1")); + } + + /** + * Test of log4j.throwableRenderer support. See bug 45721. + */ + @Test + @UsingStatusListener + void testThrowableRenderer(ListStatusListener listener) { + DOMConfigurator.configure(DOMTestCase.class.getResource("/DOMTestCase/testThrowableRenderer.xml")); + assertThat(listener.findStatusData(org.apache.logging.log4j.Level.WARN)) + .anySatisfy(status -> assertThat(status.getMessage().getFormattedMessage()) + .contains("Log4j 1 throwable renderers are not supported")); + } + + @Test + @SetTestProperty(key = "log4j1.compatibility", value = "false") + void when_compatibility_disabled_configurator_is_no_op() { + final Logger rootLogger = Logger.getRootLogger(); + final Logger logger = Logger.getLogger("org.apache.log4j.xml"); + assertThat(logger.getLevel()).isNull(); + final URL configURL = DOMTestCase.class.getResource("/DOMTestCase/DOMTestCase1.xml"); + DOMConfigurator.configure(configURL); + + assertThat(rootLogger.getAppender("A1")).isNull(); + assertThat(rootLogger.getAppender("A2")).isNull(); + assertThat(rootLogger.getLevel()).isNotEqualTo(Level.TRACE); + + assertThat(logger.getAppender("A1")).isNull(); + assertThat(logger.getLevel()).isNull(); + } +} 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..58e9d8afb73 --- /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 static org.apache.logging.log4j.util.Strings.toRootUpperCase; + +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 final String TRACE_STR = "TRACE"; + private static final 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 = toRootUpperCase(sArg); + + 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/DOMTestCase/DOMTestCase1.xml b/log4j-1.2-api/src/test/resources/DOMTestCase/DOMTestCase1.xml new file mode 100644 index 00000000000..d637afad3b9 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/DOMTestCase/DOMTestCase1.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/DOMTestCase/defaultInit.xml b/log4j-1.2-api/src/test/resources/DOMTestCase/defaultInit.xml new file mode 100644 index 00000000000..868e66ae8de --- /dev/null +++ b/log4j-1.2-api/src/test/resources/DOMTestCase/defaultInit.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/DOMTestCase/testReset.xml b/log4j-1.2-api/src/test/resources/DOMTestCase/testReset.xml new file mode 100644 index 00000000000..b0e565008e0 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/DOMTestCase/testReset.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/DOMTestCase/testThrowableRenderer.xml b/log4j-1.2-api/src/test/resources/DOMTestCase/testThrowableRenderer.xml new file mode 100644 index 00000000000..50813462379 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/DOMTestCase/testThrowableRenderer.xml @@ -0,0 +1,24 @@ + + + + + + + + + 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..0121fbe931c 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,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,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..1c68863f210 100644 --- a/log4j-1.2-api/src/test/resources/L7D_fr.properties +++ b/log4j-1.2-api/src/test/resources/L7D_fr.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,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..a6af2511118 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,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,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..9593823b24b --- /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..78d3af5a423 --- /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..9fc31300b5f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/LOG4J2-3326.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.ListAppender +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.appender.CUSTOM.filter.2.levelMin=INFO +log4j.appender.CUSTOM.filter.2.levelMax=ERROR +log4j.appender.CUSTOM.filter.3=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..b0634222d3c --- /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/PropertyConfiguratorTest/badEscape.properties b/log4j-1.2-api/src/test/resources/PropertyConfiguratorTest/badEscape.properties new file mode 100644 index 00000000000..14f70635008 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/PropertyConfiguratorTest/badEscape.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. +# +## +# This file is intentionally broken +log4j.rootLogger=\uXX41 diff --git a/log4j-1.2-api/src/test/resources/PropertyConfiguratorTest/filter.properties b/log4j-1.2-api/src/test/resources/PropertyConfiguratorTest/filter.properties new file mode 100644 index 00000000000..0a65181c088 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/PropertyConfiguratorTest/filter.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.rootLogger=info, 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..1fbe7b51a3b 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..fc525df71b7 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..a40de161bd2 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..d57e5846ee1 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 # @@ -28,4 +27,4 @@ log4j.logger.org.apache.hadoop.crytpo.key.kms.server=ALL log4j.logger.com.sun.jersey.server.wadl.generators.WadlGeneratorJAXBGrammarGenerator=OFF log4j.logger.org.apache.hadoop.security=OFF log4j.logger.org.apache.directory.server.core=OFF -log4j.logger.org.apache.hadoop.util.NativeCodeLoader=OFF \ No newline at end of file +log4j.logger.org.apache.hadoop.util.NativeCodeLoader=OFF 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..29f00dda73e 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 # @@ -28,4 +27,4 @@ log4j.rootLogger=INFO, stdout log4j.logger.org.apache.directory=OFF log4j.logger.org.apache.directory.server.kerberos=INFO, stdout log4j.additivity.org.apache.directory=false -log4j.logger.net.sf.ehcache=INFO, stdout \ No newline at end of file +log4j.logger.net.sf.ehcache=INFO, stdout 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..a40de161bd2 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..6c2fa4cb336 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..58db1c8842d 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..6c2fa4cb336 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..396985af1e5 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..3bd94a6e2c2 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..83dd3f2fd9a 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,34 +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 log4j.threshold=ALL log4j.appender.stdout=org.apache.log4j.ConsoleAppender 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..3672426f7e6 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..b7ca6914295 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..898a262af83 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..b7ca6914295 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..cd739b65be0 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 @@ -60,4 +61,4 @@ log4j.logger.org.apache.curator.framework.imps=WARN log4j.logger.org.apache.curator.framework.state.ConnectionStateManager=ERROR log4j.logger.org.apache.directory.api.ldap=ERROR -log4j.logger.org.apache.directory.server=ERROR \ No newline at end of file +log4j.logger.org.apache.directory.server=ERROR 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..b7ca6914295 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..b7ca6914295 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..b7ca6914295 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..898a262af83 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..b7ca6914295 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..b7ca6914295 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..d7d2c2527b7 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..a2a9f6e98c7 --- /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-EnhancedRollingFileAppender.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-EnhancedRollingFileAppender.properties new file mode 100644 index 00000000000..a0ef860d21c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-EnhancedRollingFileAppender.properties @@ -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. +# + +############################################################################### +# +# Log4J 1.2 Configuration. +# + +log4j.rootLogger=TRACE, DEFAULT_TIME, DEFAULT_SIZE, TIME, SIZE + +log4j.appender.DEFAULT_TIME = org.apache.log4j.rolling.RollingFileAppender +log4j.appender.DEFAULT_TIME.layout = org.apache.log4j.SimpleLayout +log4j.appender.DEFAULT_TIME.File = target/EnhancedRollingFileAppender/defaultTime.log +log4j.appender.DEFAULT_TIME.rollingPolicy = org.apache.log4j.rolling.TimeBasedRollingPolicy +log4j.appender.DEFAULT_TIME.rollingPolicy.FileNamePattern = target/EnhancedRollingFileAppender/defaultTime.%d{yyyy-MM-dd}.log + +log4j.appender.DEFAULT_SIZE = org.apache.log4j.rolling.RollingFileAppender +log4j.appender.DEFAULT_SIZE.File = target/EnhancedRollingFileAppender/defaultSize.log +log4j.appender.DEFAULT_SIZE.layout = org.apache.log4j.SimpleLayout +log4j.appender.DEFAULT_SIZE.triggeringPolicy = org.apache.log4j.rolling.SizeBasedTriggeringPolicy +log4j.appender.DEFAULT_SIZE.rollingPolicy = org.apache.log4j.rolling.FixedWindowRollingPolicy +log4j.appender.DEFAULT_SIZE.rollingPolicy.FileNamePattern = target/EnhancedRollingFileAppender/defaultSize.%i.log + +log4j.appender.TIME = org.apache.log4j.rolling.RollingFileAppender +log4j.appender.TIME.Append = false +log4j.appender.TIME.BufferedIO = true +log4j.appender.TIME.BufferSize = 1000 +log4j.appender.TIME.File = target/EnhancedRollingFileAppender/ignoredTime.log +log4j.appender.TIME.ImmediateFlush = false +log4j.appender.TIME.layout = org.apache.log4j.SimpleLayout +log4j.appender.TIME.triggeringPolicy = org.apache.log4j.rolling.TimeBasedRollingPolicy +# It is explicitly not a TimeBasedRolling +log4j.appender.TIME.rollingPolicy = org.apache.log4j.rolling.FixedWindowRollingPolicy +log4j.appender.TIME.rollingPolicy.ActiveFileName = target/EnhancedRollingFileAppender/time.log +log4j.appender.TIME.rollingPolicy.FileNamePattern = target/EnhancedRollingFileAppender/time.%d{yyyy-MM-dd}.log + +log4j.appender.SIZE = org.apache.log4j.rolling.RollingFileAppender +log4j.appender.SIZE.Append = false +log4j.appender.SIZE.BufferedIO = true +log4j.appender.SIZE.BufferSize = 1000 +log4j.appender.SIZE.File = target/EnhancedRollingFileAppender/ignoredSize.log +log4j.appender.SIZE.ImmediateFlush = false +log4j.appender.SIZE.layout = org.apache.log4j.SimpleLayout +log4j.appender.SIZE.FileName = target/EnhancedRollingFileAppender/size.log +log4j.appender.SIZE.triggeringPolicy = org.apache.log4j.rolling.SizeBasedTriggeringPolicy +log4j.appender.SIZE.triggeringPolicy.MaxFileSize = 10000000 +log4j.appender.SIZE.rollingPolicy = org.apache.log4j.rolling.FixedWindowRollingPolicy +log4j.appender.SIZE.rollingPolicy.ActiveFileName = target/EnhancedRollingFileAppender/size.log +log4j.appender.SIZE.rollingPolicy.FileNamePattern = target/EnhancedRollingFileAppender/size.%i.log +log4j.appender.SIZE.rollingPolicy.MinIndex = 11 +log4j.appender.SIZE.rollingPolicy.MaxIndex = 20 diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-EnhancedRollingFileAppender.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-EnhancedRollingFileAppender.xml new file mode 100644 index 00000000000..a568dc21ca2 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-EnhancedRollingFileAppender.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..56de9df9711 --- /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-LevelRangeFilter.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-LevelRangeFilter.properties new file mode 100644 index 00000000000..b84788b417d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-LevelRangeFilter.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.filter.1 = org.apache.log4j.varia.LevelRangeFilter +log4j.appender.LIST.filter.1.LevelMin = INFO +log4j.appender.LIST.filter.1.LevelMax = ERROR +log4j.appender.LIST.filter.1.AcceptOnMatch = false +log4j.rootLogger = debug, LIST diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-LevelRangeFilter.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-LevelRangeFilter.xml new file mode 100644 index 00000000000..ab4cdd7e5a4 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-LevelRangeFilter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + 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..bb3bc83654b 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..f030cb88371 --- /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..9b897aed81f 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..911ab683653 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..2ef6d18cbdd --- /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..c57d69690bf --- /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..272b79fc8d0 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..82bc2b82084 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..65c64d7fac1 --- /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..400c9e2172b 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..36aeeb3938e --- /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..139d25bde2e 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..e1dd2a91c87 --- /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..fce9cd926a2 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..7d498484322 --- /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..011cef594b3 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..574716eb5c9 --- /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..bcf117148aa 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..b8815d325dd --- /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..2def3f6d6a3 --- /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..5cf42b5b637 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..90b3f3095ff --- /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..3171c4066c6 --- /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..82c48a0dfb7 --- /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..4aebc427819 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..2ed5554337e --- /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..f635f194276 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..116859ae741 --- /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 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..6365f63b8bd 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..e9feeb64f04 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..e8abecea806 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..d919270cf2b 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..bad5156b0f1 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark_project.jetty=WARN - 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark_project.jetty=WARN - 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..49618ae7b54 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark-project.jetty=WARN - 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark_project.jetty=WARN - 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..798457fae65 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..3e7f668307e 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark_project.jetty=WARN - 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..2472d7dd173 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..c216bec215d 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..4f31a6aca58 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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..001d0482aca 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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, @@ -25,4 +25,3 @@ log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{ # Ignore messages below warning level from Jetty, because it's a bit verbose log4j.logger.org.spark_project.jetty=WARN - 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..29ec90539be 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 @@ -2,11 +2,11 @@ # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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 # -# 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/hello.vm b/log4j-1.2-api/src/test/resources/hello.vm deleted file mode 100644 index 5ce97550285..00000000000 --- a/log4j-1.2-api/src/test/resources/hello.vm +++ /dev/null @@ -1,6 +0,0 @@ - - - #set( $foo = "Velocity" ) -Hello $foo World! - - \ No newline at end of file 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..7de6461c4a8 100644 --- a/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml +++ b/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml @@ -1,22 +1,21 @@ - + @@ -44,4 +43,4 @@ - \ No newline at end of file + 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..556a4c1bd69 --- /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 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..092d342f549 --- /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..478a92e0311 --- /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..1a81ab8d13d --- /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/fallback1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/fallback1.properties new file mode 100644 index 00000000000..fb4bfc8cc2c --- /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/hierarchyThreshold1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold1.properties new file mode 100644 index 00000000000..e9260a12c5e --- /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 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..ff042bf71fa --- /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 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..ce35952b4d7 --- /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 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..e503cf83521 --- /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 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..d870d80d18d --- /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 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..a77dae331bf --- /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 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..8602ac3c11c --- /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..6408afd4512 --- /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 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..e228b49dbb0 --- /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..3c2b837eb27 --- /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..e8eef911699 --- /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..67bb7177627 --- /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..d1753a9d953 --- /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..6998d503b55 --- /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..c7691decbe8 --- /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..e9b58b1678e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout15.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=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..327175c15b7 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout16.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=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..a863c9d5379 --- /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..093faca9f5a --- /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..5d09b4aa5dc --- /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..5b0f3ceda41 --- /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..cd4f7c5f864 --- /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..00b3f7131a2 --- /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..67cde918108 --- /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..7ca09b8e4d1 --- /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..8e9b136261e --- /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..aed43bbf02a --- /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 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..73bdba89c4e --- /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 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..8d985f8149f --- /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 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..85c9c4b8365 --- /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 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..211a430e38d --- /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 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..495775ba424 --- /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..c6dc606e75e --- /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..5a413b4e81f --- /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 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..f03a494202a --- /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 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..20176088407 --- /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 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..aaa7c220838 --- /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 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..7c0c6f33b3a --- /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..03272e5cf1f --- /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 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..b59c7a45bd9 --- /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 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..e4fea1cf1b5 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer1.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 %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..9121f610039 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer2.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/socketServer3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer3.properties new file mode 100644 index 00000000000..3cbea3c040f --- /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..f5fb662484e --- /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..4640cd3016c --- /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..a5cb07d919a --- /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..fd07c4ef21a --- /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..87b9cd03406 --- /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/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..0121fbe931c --- /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..1c68863f210 --- /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..a6af2511118 --- /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..3d251346fbd --- /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..f5962dab3b8 --- /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..3d251346fbd --- /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..503a548156b --- /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..62f872027e9 --- /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..7a96f185053 --- /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..c0d481e9353 --- /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..f514396ff2a --- /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..60333593aa9 --- /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..2eee705c0dd --- /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..a1a213b267b --- /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..5405fcddbdd --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-file-2.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=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..cba42154bef --- /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..8860b6e050a --- /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..478a92e0311 --- /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..fb4a1c799d3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-mapRewrite.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..bd0ea8c7bad --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-rewrite.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + 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..5d6dbde2d5e --- /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..33910d18e13 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 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..d72784d23dc --- /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..151ca7ed5d2 --- /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..c9c7fad9550 --- /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..967644f2233 --- /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:${syslog.port} +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..be6a4887460 --- /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:${syslog.port} +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..9d065e2e15c --- /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..3786306ed86 --- /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:${syslog.port} +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..c1defbdd5f7 --- /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..46d8a0d2d71 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + 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..8b47ed485e9 100644 --- a/log4j-1.2-api/src/test/resources/log4j2-config.xml +++ b/log4j-1.2-api/src/test/resources/log4j2-config.xml @@ -1,22 +1,21 @@ - + @@ -36,4 +35,4 @@ - \ No newline at end of file + diff --git a/log4j-1.2-api/src/test/resources/logWithMDC.xml b/log4j-1.2-api/src/test/resources/logWithMDC.xml index 1fe848295fe..93ad097c59d 100644 --- a/log4j-1.2-api/src/test/resources/logWithMDC.xml +++ b/log4j-1.2-api/src/test/resources/logWithMDC.xml @@ -1,22 +1,21 @@ - + @@ -37,4 +36,4 @@ - \ No newline at end of file + diff --git a/log4j-api-java9/.gitignore b/log4j-api-java9/.gitignore new file mode 100644 index 00000000000..ae3c1726048 --- /dev/null +++ b/log4j-api-java9/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/log4j-api-java9/pom.xml b/log4j-api-java9/pom.xml index 183ad14dd3c..5112014f8ac 100644 --- a/log4j-api-java9/pom.xml +++ b/log4j-api-java9/pom.xml @@ -1,153 +1,104 @@ 4.0.0 org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent log4j-api-java9 pom Apache Log4j API Java 9 support The Apache Log4j API (Java 9) - ${basedir}/.. - API Documentation - /api + true + 9 - junit - junit + org.assertj + assertj-core test - org.apache.maven - maven-core + org.junit.jupiter + junit-jupiter-engine test - org.apache.maven.plugins - maven-toolchains-plugin - 1.1 + maven-assembly-plugin + zip - toolchain + single + package + + log4j-api-java9-${project.version} + false + + src/assembly/java9.xml + + - - - - 9 - - - + org.apache.maven.plugins maven-compiler-plugin default-compile - compile compile default-test-compile - test-compile testCompile + test-compile - - 9 - 9 - 9 - none - + org.apache.maven.plugins maven-surefire-plugin - - 2.13 + - test - test + run-tests 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 index 3e595612db4..0aa94e150fb 100644 --- a/log4j-api-java9/src/assembly/java9.xml +++ b/log4j-api-java9/src/assembly/java9.xml @@ -1,23 +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. + --> src @@ -29,22 +26,10 @@ ${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 + org/apache/logging/log4j/util/Base64Util.class + org/apache/logging/log4j/util/ProcessIdUtil.class + org/apache/logging/log4j/util/StackLocator.class + org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.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 index 24012e6ae3a..a2b60d2fe25 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j; @@ -20,5 +20,4 @@ * 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 { -} +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 index 082e36ea3e7..343013dcd45 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -20,5 +20,4 @@ * 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 { -} +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 index 8b1af5dd565..2cef3a24ece 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -21,7 +21,5 @@ * be copied into the log4j-api module. */ public class ThreadDumpMessage { - public static interface ThreadInfoFactory { - - } + 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 index c3a24e282bf..25f4cf4d22e 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.simple; @@ -20,5 +20,4 @@ * 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 { -} +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 index 65b86380e9e..2f6fbdf3149 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -20,5 +20,4 @@ * 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 { -} +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 index d53dc43e753..0004af0bfb1 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.status; @@ -20,5 +20,4 @@ * 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 { -} +public class Dummy {} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/Base64Util.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/Base64Util.java new file mode 100644 index 00000000000..ad66303df38 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/Base64Util.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.util; + +import java.nio.charset.Charset; +import java.util.Base64; + +/** + * Base64 encodes Strings. This utility is only necessary because the mechanism to do this changed in Java 8 and + * the original method for Base64 encoding was removed in Java 9. + */ +public final class Base64Util { + + private static final Base64.Encoder encoder = Base64.getEncoder(); + + private Base64Util() {} + + public static String encode(final String str) { + return str != null ? encoder.encodeToString(str.getBytes(Charset.defaultCharset())) : null; + } +} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java new file mode 100644 index 00000000000..bf78f35df57 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java @@ -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. + */ +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 EnvironmentPropertySource implements PropertySource {} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java new file mode 100644 index 00000000000..6eb2c710425 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/LoaderUtil.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; + +/** + * 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 final class LoaderUtil { + + public static ClassLoader getThreadContextClassLoader() { + return LoaderUtil.class.getClassLoader(); + } +} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.java new file mode 100644 index 00000000000..9fb9a4ee56b --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.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.util; + +import java.util.Deque; + +/** + * This is a dummy class and is only here to allow this module to compile. It will not + * be copied into the log4j-api module. + */ +final class PrivateSecurityManagerStackTraceUtil { + + static boolean isEnabled() { + return false; + } + + static Deque> getCurrentStackTrace() { + return null; + } +} 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 index e467413db25..ad6666d5977 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -23,7 +23,7 @@ public class ProcessIdUtil { public static String getProcessId() { try { return Long.toString(ProcessHandle.current().pid()); - } catch(Exception ex) { + } catch (Exception ex) { return DEFAULT_PROCESSID; } } diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java new file mode 100644 index 00000000000..56e6259b5d8 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/PropertySource.java @@ -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. + */ +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 interface PropertySource {} 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 index b0661d0fe14..e928777ed6b 100644 --- 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 @@ -1,82 +1,107 @@ /* * 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.logging.log4j.util; -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.Predicate; /** * Consider this class private. Determines the caller's class. */ -public class StackLocator { +public final class StackLocator { - private final static StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - private final static StackWalker stackWalker = StackWalker.getInstance(); - - private final static StackLocator INSTANCE = new StackLocator(); + private static final StackWalker STACK_WALKER = StackWalker.getInstance(); + private static final StackLocator INSTANCE = new StackLocator(); public static StackLocator getInstance() { return INSTANCE; } - private StackLocator() { - } + private StackLocator() {} - public Class getCallerClass(final String fqcn) { - return getCallerClass(fqcn, ""); + 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)); } 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); + 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); + 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); + 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 Deque> getCurrentStackTrace() { + // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) + if (PrivateSecurityManagerStackTraceUtil.isEnabled()) { + return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace(); + } + final Deque> stack = new ArrayDeque>(); + return WALKER.walk(s -> { + s.forEach(f -> stack.add(f.getDeclaringClass())); + 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 + 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()) - .get() - .toStackTraceElement(); + .map(StackWalker.StackFrame::toStackTraceElement) + .orElse(null); } public StackTraceElement getStackTraceElement(final int depth) { - return stackWalker.walk(s -> s.skip(depth).findFirst()).get().toStackTraceElement(); + return STACK_WALKER + .walk(s -> s.skip(depth).findFirst()) + .map(StackWalker.StackFrame::toStackTraceElement) + .orElse(null); } } diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java new file mode 100644 index 00000000000..ec8a4792945 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java @@ -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. + */ +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 SystemPropertiesPropertySource implements PropertySource {} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java index 6e7cee5278a..df62f71e5f7 100644 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java @@ -1,51 +1,35 @@ /* * 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.logging.log4j.util.internal; +import static org.apache.logging.log4j.util.internal.SerializationUtil.REQUIRED_JAVA_CLASSES; +import static org.apache.logging.log4j.util.internal.SerializationUtil.REQUIRED_JAVA_PACKAGES; + import java.io.ObjectInputFilter; -import java.util.Arrays; -import java.util.List; public class DefaultObjectInputFilter implements ObjectInputFilter { - - private static final List REQUIRED_JAVA_CLASSES = Arrays.asList( - "java.math.BigDecimal", - "java.math.BigInteger", - // for Message delegate - "java.rmi.MarshalledObject", - "[B" - ); - - private static final List REQUIRED_JAVA_PACKAGES = Arrays.asList( - "java.lang.", - "java.time", - "java.util.", - "org.apache.logging.log4j.", - "[Lorg.apache.logging.log4j." - ); - private final ObjectInputFilter delegate; public DefaultObjectInputFilter() { delegate = null; } - public DefaultObjectInputFilter(ObjectInputFilter filter) { + public DefaultObjectInputFilter(final ObjectInputFilter filter) { delegate = filter; } @@ -54,21 +38,20 @@ 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) { - Status status = null; + public Status checkInput(final FilterInfo filterInfo) { + Status status; if (delegate != null) { status = delegate.checkInput(filterInfo); if (status != Status.UNDECIDED) { return status; } } - ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); + final ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); if (serialFilter != null) { status = serialFilter.checkInput(filterInfo); if (status != Status.UNDECIDED) { @@ -76,11 +59,15 @@ public Status checkInput(FilterInfo filterInfo) { return status; } } - if (filterInfo.serialClass() != null) { - String name = filterInfo.serialClass().getName(); - if (isAllowedByDefault(name) || isRequiredPackage(name)) { + final Class serialClass = filterInfo.serialClass(); + if (serialClass != null) { + final String name = SerializationUtil.stripArray(serialClass); + if (isAllowedByDefault(name)) { return Status.ALLOWED; } + } else { + // Object already deserialized + return Status.ALLOWED; } return Status.REJECTED; } diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.java new file mode 100644 index 00000000000..ddade3dd8a8 --- /dev/null +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.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.util.internal; + +import java.util.List; + +/** + * Dummy class for compilation purposes only. + */ +public final class SerializationUtil { + public static final List REQUIRED_JAVA_CLASSES = List.of(); + public static final List REQUIRED_JAVA_PACKAGES = List.of(); + + public static String stripArray(final Class clazz) { + return null; + } +} 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-java9/src/test/java/org/apache/logging/log4j/util/java9/ProcessIdUtilTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ProcessIdUtilTest.java new file mode 100644 index 00000000000..1a24860f57d --- /dev/null +++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/ProcessIdUtilTest.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.util.java9; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.apache.logging.log4j.util.ProcessIdUtil; +import org.junit.jupiter.api.Test; + +class ProcessIdUtilTest { + + @Test + void processIdTest() { + final String processId = ProcessIdUtil.getProcessId(); + assertNotEquals(processId, ProcessIdUtil.DEFAULT_PROCESSID, "ProcessId is default"); + } +} diff --git a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/StackLocatorTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/StackLocatorTest.java new file mode 100644 index 00000000000..cc8f61218a7 --- /dev/null +++ b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/java9/StackLocatorTest.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.logging.log4j.util.java9; + +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.assertSame; + +import java.util.Deque; +import java.util.Stack; +import org.apache.logging.log4j.util.StackLocator; +import org.junit.jupiter.api.Test; + +class StackLocatorTest { + + @Test + void testGetCallerClass() { + final Class expected = StackLocatorTest.class; + final StackLocator stackLocator = StackLocator.getInstance(); + final Class actual = stackLocator.getCallerClass(1); + assertSame(expected, actual); + } + + @Test + void testGetCallerClassNameViaStackTrace() throws Exception { + final Class expected = StackLocatorTest.class; + final Class actual = Class.forName(new Throwable().getStackTrace()[0].getClassName()); + assertSame(expected, actual); + } + + @Test + void testGetCurrentStackTrace() { + final StackLocator stackLocator = StackLocator.getInstance(); + final Deque> classes = stackLocator.getCurrentStackTrace(); + final Stack> reversed = new Stack<>(); + reversed.ensureCapacity(classes.size()); + while (!classes.isEmpty()) { + reversed.push(classes.removeLast()); + } + while (reversed.peek() != StackLocator.class) { + reversed.pop(); + } + reversed.pop(); // ReflectionUtil + assertSame(StackLocatorTest.class, reversed.pop()); + } + + @Test + void testGetCallerClassViaName() { + Inner.assertCallerClassViaName(); + } + + @Test + void testGetCallerClassViaAnchorClass() { + Inner.assertCallerClassViaAnchorClass(); + } + + private static class Inner { + private static void assertCallerClassViaName() { + final Class expected = StackLocatorTest.class; + final StackLocator stackLocator = StackLocator.getInstance(); + final Class actual = stackLocator.getCallerClass(Inner.class.getName(), ""); + assertSame(expected, actual); + } + + private static void assertCallerClassViaAnchorClass() { + final Class expected = StackLocatorTest.class; + final StackLocator stackLocator = StackLocator.getInstance(); + final Class actual = stackLocator.getCallerClass(Inner.class); + assertSame(expected, actual); + } + } + + @Test + void testLocateClass() { + final ClassLocator locator = new ClassLocator(); + final Class clazz = locator.locateClass(); + assertNotNull(clazz, "Could not locate class"); + assertEquals(this.getClass(), clazz, "Incorrect class"); + } + + private final class Foo { + + private StackTraceElement foo() { + return new Bar().bar(); // <--- testCalcLocation() line + } + } + + private final class Bar { + + private StackTraceElement bar() { + return baz(); + } + + private StackTraceElement baz() { + return quux(); + } + } + + private StackTraceElement quux() { + final StackLocator stackLocator = StackLocator.getInstance(); + return stackLocator.calcLocation("org.apache.logging.log4j.util.java9.StackLocatorTest$Bar"); + } + + @Test + void testCalcLocation() { + /* + * We are setting up a stack trace that looks like: + * - org.apache.logging.log4j.util.test.StackLocatorTest#quux(line:118) + * - org.apache.logging.log4j.util.test.StackLocatorTest$Bar#baz(line:112) + * - org.apache.logging.log4j.util.test.StackLocatorTest$Bar#bar(line:108) + * - org.apache.logging.log4j.util.test.StackLocatorTest$Foo(line:100) + * + * We are pretending that org.apache.logging.log4j.util.test.StackLocatorTest$Bar is the logging class, and + * org.apache.logging.log4j.util.test.StackLocatorTest$Foo is where the log line emanated. + */ + final StackTraceElement element = new Foo().foo(); + assertEquals("org.apache.logging.log4j.util.java9.StackLocatorTest$Foo", element.getClassName()); + // The line number below may need adjustment if this file is changed. + assertEquals(99, element.getLineNumber()); + } + + @Test + void testTopElementInStackTrace() { + final StackLocator stackLocator = StackLocator.getInstance(); + final Deque> classes = stackLocator.getCurrentStackTrace(); + assertSame(StackLocator.class, classes.getFirst()); + } + + @Test + void testCalcLocationWhenNotInTheStack() { + final StackLocator stackLocator = StackLocator.getInstance(); + final StackTraceElement stackTraceElement = stackLocator.calcLocation("java.util.Logger"); + assertNull(stackTraceElement); + } + + static class ClassLocator { + + public Class locateClass() { + final StackLocator stackLocator = StackLocator.getInstance(); + 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..aef8a1b5d64 --- /dev/null +++ b/log4j-api-test/pom.xml @@ -0,0 +1,182 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + log4j-api-test + jar + Apache Log4j API Tests + The Apache Log4j API Test + + + 9 + + + org.apache.logging.log4j.test + + org.apache.commons.lang3.*;resolution:=optional, + org.assertj.*;resolution:=optional, + + org.junit.*;resolution:=optional, + org.hamcrest.*;resolution:=optional, + org.junitpioneer.*;resolution:=optional, + org.apache.maven.*;resolution:=optional, + org.codehaus.plexus.util.*;resolution:=optional, + org.mockito.*;resolution:=optional + + + + junit;transitive=false, + org.hamcrest;transitive=false, + org.junit.jupiter.api;transitive=false, + org.junitpioneer;transitive=false, + + maven.core;substitute="maven-core";transitive=false;static=true, + maven.model;substitute="maven-model";transitive=false;static=true, + maven.model.builder;substitute="maven-model-builder";transitive=false;static=true, + plexus.utils;substitute="plexus-utils";transitive=false;static=true + + + + + + org.apache.logging.log4j + log4j-api + + + org.apache.commons + commons-lang3 + + + org.hamcrest + hamcrest + + + junit + junit + + + org.junit.jupiter + junit-jupiter-api + + + org.junit-pioneer + junit-pioneer + + + org.junit.platform + junit-platform-commons + + + org.apache.maven + maven-core + + + org.apache.maven + maven-model + + + org.codehaus.plexus + plexus-utils + + + org.assertj + assertj-core + + + + com.fasterxml.jackson.core + jackson-core + test + + + + com.fasterxml.jackson.core + jackson-databind + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.mockito + mockito-core + + + org.mockito + mockito-inline + test + + + org.jspecify + jspecify + test + + + + org.osgi + org.osgi.core + test + + + uk.org.webcompere + system-stubs-core + test + + + uk.org.webcompere + system-stubs-jupiter + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + performance,smoke + + + + + + 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..a2b67b64889 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/AbstractSerializationTest.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.test; + +import static org.apache.logging.log4j.test.SerializableMatchers.serializesRoundTrip; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.Serializable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Subclasses tests {@link Serializable} objects. + */ +@RunWith(Parameterized.class) +public abstract class AbstractSerializationTest { + + private final Serializable serializable; + + public AbstractSerializationTest(final Serializable serializable) { + this.serializable = serializable; + } + + @Test + public void testSerializationRoundtripEquals() { + assertThat(serializable, serializesRoundTrip(serializable)); + } + + @Test + public void testSerializationRoundtripNoException() { + assertThat(serializable, serializesRoundTrip()); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ListStatusListener.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ListStatusListener.java new file mode 100644 index 00000000000..fb19e5b7aef --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ListStatusListener.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; + +import java.util.stream.Stream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.osgi.annotation.versioning.ProviderType; + +/** + * A {@link StatusListener} that collects messages for further inspection. + */ +@ProviderType +public interface ListStatusListener extends StatusListener { + + void clear(); + + Stream getStatusData(); + + default Stream findStatusData(Level level) { + return getStatusData().filter(data -> level.isLessSpecificThan(data.getLevel())); + } + + default Stream findStatusData(Level level, String regex) { + return findStatusData(level) + .filter(data -> data.getMessage().getFormattedMessage().matches(regex)); + } +} 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 82% 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..6fc46f6f936 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 @@ -1,30 +1,29 @@ /* * 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.logging.log4j; +package org.apache.logging.log4j.test; -import java.io.Serializable; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsInstanceOf.any; +import java.io.Serializable; import org.apache.commons.lang3.SerializationUtils; import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.hamcrest.core.IsInstanceOf.any; - /** * Hamcrest Matchers for Serializable classes. * @@ -53,6 +52,5 @@ public static Matcher serializesRoundTrip() { return serializesRoundTrip(any(Serializable.class)); } - private SerializableMatchers() { - } + private SerializableMatchers() {} } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java new file mode 100644 index 00000000000..e95f1e9bb67 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.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.test; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +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; + +/** + * + */ +public class TestLogger extends AbstractLogger { + + private static final long serialVersionUID = 1L; + + public TestLogger() {} + + public TestLogger(final String name, final MessageFactory messageFactory) { + super(name, messageFactory); + } + + public TestLogger(final String name) { + super(name); + } + + private final List list = new ArrayList<>(); + + public List getEntries() { + return list; + } + + @Override + public void logMessage( + final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { + log(level, marker, fqcn, (StackTraceElement) null, msg, throwable); + } + + @Override + @SuppressFBWarnings("INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE") + 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); + } + sb.append(' '); + sb.append(level.toString()); + sb.append(' '); + if (location != null) { + sb.append(location); + sb.append(' '); + } + sb.append(message.getFormattedMessage()); + final Map mdc = ThreadContext.getImmutableContext(); + if (!mdc.isEmpty()) { + sb.append(' '); + sb.append(mdc); + sb.append(' '); + } + final Object[] params = message.getParameters(); + 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; + } + if (t != null) { + sb.append(' '); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + t.printStackTrace(new PrintStream(baos)); + sb.append(baos); + } + list.add(sb.toString()); + // System.out.println(sb.toString()); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String msg) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String msg, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String msg, final Object... p1) { + 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 boolean isEnabled(final Level level, final Marker marker, final CharSequence msg, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Object msg, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Message msg, final Throwable t) { + return true; + } + + @Override + public Level getLevel() { + return Level.ALL; + } +} 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 83% 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..d2b6ea7b587 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 @@ -1,27 +1,26 @@ /* * 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.logging.log4j; +package org.apache.logging.log4j.test; import java.util.HashMap; import java.util.Map; - 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; /** * @@ -33,7 +32,7 @@ public class TestLoggerContext implements LoggerContext { public ExtendedLogger getLogger(final String name) { final ExtendedLogger extendedLogger = map.get(name); if (extendedLogger != null) { - return extendedLogger; + return extendedLogger; } final ExtendedLogger logger = new TestLogger(name); map.put(name, logger); @@ -64,5 +63,4 @@ public boolean hasLogger(final String name, final MessageFactory messageFactory) public boolean hasLogger(final String name, final Class messageFactoryClass) { return false; } - } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.java new file mode 100644 index 00000000000..132b04ee707 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.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.test; + +import java.net.URI; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.spi.LoggerContextFactory; + +/** + * + */ +public class TestLoggerContextFactory implements LoggerContextFactory { + + private static final LoggerContext context = new TestLoggerContext(); + + @Override + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { + return context; + } + + @Override + public LoggerContext getContext( + final String fqcn, + final ClassLoader loader, + final Object externalContext, + final boolean currentContext, + final URI configLocation, + final String name) { + return context; + } + + @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..9942990520c --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestProperties.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; + +/** + * A container for per-test properties. + */ +public interface TestProperties { + + /** + * Path to a directory specific to the test class, + */ + public static final String LOGGING_PATH = "logging.path"; + + 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 85% 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..2407e237307 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 @@ -1,37 +1,36 @@ /* * 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.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; /** * Holds an immutable copy of the ThreadContext stack and map. - * + * * TODO Use LOG4J2-1517 Add ThreadContext.setContext(Map) - * + * * or - * + * * TODO Might be replaced by something from LOG4J2-1447. - * + * * or do nothing. - * + * * @since 2.7 */ public class ThreadContextHolder { @@ -43,7 +42,7 @@ public class ThreadContextHolder { /** * Constructs a new holder initialized with an immutable copy of the ThreadContext stack and map. - * + * * @param restoreContext * @param restoreStack */ 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 77% 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..eed1952c324 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 @@ -1,32 +1,35 @@ /* * 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.logging.log4j; +package org.apache.logging.log4j.test; -import java.util.Map; +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; -import org.apache.logging.log4j.Timer; +import java.util.Map; import org.apache.logging.log4j.ThreadContext; -import static org.junit.Assert.*; - - +import org.apache.logging.log4j.util.Timer; public class ThreadContextUtilityClass { - public static void perfTest() throws Exception { + public static void perfTest() { ThreadContext.clearMap(); final Timer complete = new Timer("ThreadContextTest"); complete.start(); @@ -53,13 +56,11 @@ public static void perfTest() throws Exception { System.out.println(complete.toString()); } - public static void testGetContextReturnsEmptyMapIfEmpty() { ThreadContext.clearMap(); assertTrue(ThreadContext.getContext().isEmpty()); } - public static void testGetContextReturnsMutableCopy() { ThreadContext.clearMap(); final Map map1 = ThreadContext.getContext(); @@ -87,18 +88,17 @@ public static void testGetImmutableContextReturnsEmptyMapIfEmpty() { assertTrue(ThreadContext.getImmutableContext().isEmpty()); } - 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() { @@ -106,11 +106,14 @@ public static void testGetImmutableStackReturnsEmptyStackIfEmpty() { assertTrue(ThreadContext.getImmutableStack().asList().isEmpty()); } - public static void testPut() { ThreadContext.clearMap(); assertNull(ThreadContext.get("testKey")); 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/AbstractFileCleaner.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractFileCleaner.java new file mode 100644 index 00000000000..268c9318d61 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractFileCleaner.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.test.junit; + +import static org.junit.jupiter.api.Assertions.fail; + +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.stream.Collectors; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +abstract class AbstractFileCleaner implements BeforeEachCallback, AfterEachCallback { + + 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); + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + clean(context); + } + + @Override + public void afterEach(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; i < MAX_TRIES; i++) { + try { + if (delete(path)) { + failures.remove(path); + break; + } + } catch (final IOException e) { + failures.put(path, e); + } + try { + TimeUnit.MILLISECONDS.sleep(SLEEP_PERIOD_MILLIS); + } catch (final InterruptedException ignored) { + 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/BundleTestInfo.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/BundleTestInfo.java new file mode 100644 index 00000000000..6dce1d5ad1c --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/BundleTestInfo.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.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-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..841f6d1fcca --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpDirectories.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 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..7ab44f6d7b6 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpFiles.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 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..84c12b42109 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/DirectoryCleaner.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.test.junit; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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 + @SuppressFBWarnings("PATH_TRAVERSAL_IN") + 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 { + return deleteDirectory(path); + } + + static boolean deleteDirectory(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..0e82c01b8e1 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.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.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(final ExtensionContext context) { + EXTENSION_CONTEXT.set(context); + } + + private static void unbind(final ExtensionContext context) { + EXTENSION_CONTEXT.set(context.getParent().orElse(null)); + } + + public static ExtensionContext getContext() { + return EXTENSION_CONTEXT.get(); + } + + public static ExtensionContext getContext(final ExtensionContext context) { + return context != null ? context : EXTENSION_CONTEXT.get(); + } + + public static T getAttribute(final Object key, final Class clazz, final ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + assertNotNull(actualContext, "missing ExtensionContext"); + return actualContext.getStore(LOG4J2_NAMESPACE).get(key, clazz); + } + + public static void setAttribute(final Object key, final Object value, final ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + assertNotNull(actualContext, "missing ExtensionContext"); + actualContext.getStore(LOG4J2_NAMESPACE).put(key, value); + } + + public static void removeAttribute(final Object key, final ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + if (actualContext != null) { + actualContext.getStore(LOG4J2_NAMESPACE).remove(key); + } + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + unbind(context); + } + + @Override + public void afterAll(final ExtensionContext context) throws Exception { + unbind(context); + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + bind(context); + } + + @Override + public void beforeAll(final 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..9a9500d53a6 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/FileCleaner.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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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 + @SuppressFBWarnings("PATH_TRAVERSAL_IN") + 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..abbb616dc0e --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/InitializesThreadContext.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.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 = Log4jStaticResources.THREAD_CONTEXT, mode = ResourceAccessMode.READ_WRITE) +public @interface InitializesThreadContext {} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4jStaticResources.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4jStaticResources.java new file mode 100644 index 00000000000..b387ed095ac --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4jStaticResources.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.test.junit; + +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Constants to use the {@link ResourceLock} annotation. + */ +public final class Log4jStaticResources { + + /** + * Marks tests that require access to {@link org.apache.logging.log4j.LogManager} methods or change its + * underlying {@link org.apache.logging.log4j.spi.LoggerContextFactory} implementation. + */ + public static final String LOG_MANAGER = "log4j.LogManager"; + + /** + * Marks tests that require access to {@link org.apache.logging.log4j.ThreadContext} methods or change its + * underlying {@link org.apache.logging.log4j.spi.ThreadContextMap} implementation. + */ + public static final String THREAD_CONTEXT = "log4j.ThreadContext"; + + /** + * Marks tests that require access to {@link org.apache.logging.log4j.MarkerManager} methods. + */ + public static final String MARKER_MANAGER = "log4j.MarkerManager"; + + /** + * Marks tests that requires access to {@link org.apache.logging.log4j.Level} static methods to create new levels. + */ + public static final String LEVEL = "log4j.Level"; + + /** + * Marks tests that require access to {@link org.apache.logging.log4j.status.StatusLogger} static methods or + * change its underlying implementation. + */ + public static final String STATUS_LOGGER = "log4j.StatusLogger"; + + private Log4jStaticResources() {} +} 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 77% 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..a925fedb6d2 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 @@ -1,20 +1,20 @@ /* * 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.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; @@ -31,7 +34,6 @@ public class LogManagerLoggerContextFactoryRule extends ExternalResource { private LoggerContextFactory restoreLoggerContextFactory; public LogManagerLoggerContextFactoryRule(final LoggerContextFactory loggerContextFactory) { - super(); this.loggerContextFactory = loggerContextFactory; } @@ -45,5 +47,4 @@ protected void before() throws Throwable { this.restoreLoggerContextFactory = LogManager.getFactory(); LogManager.setFactory(this.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..4e0fbf75df5 --- /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(final LoggerContextFactory loggerContextFactory) { + this.loggerContextFactory = loggerContextFactory; + } + + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + getStore(context).put(KEY, LogManager.getFactory()); + LogManager.setFactory(loggerContextFactory); + } + + @Override + public void afterAll(final ExtensionContext context) throws Exception { + LogManager.setFactory(getStore(context).get(KEY, LoggerContextFactory.class)); + } + + private ExtensionContext.Store getStore(final ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass())); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.java new file mode 100644 index 00000000000..f9f1a12185f --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.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.test.junit; + +/** + * Helper class for JUnit tests. + */ +public class Mutable { + private String value; + + public Mutable set(final String value) { + this.value = value; + return this; + } + + @Override + public String toString() { + return this.value; + } +} 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..8107ecb3dab --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SecurityManagerTestRule.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 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) { + 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-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java new file mode 100644 index 00000000000..e568e415130 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.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.test.junit; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.FilteredObjectInputStream; + +/** + * Utility class to facilitate serializing and deserializing objects. + */ +public class SerialUtil { + + private SerialUtil() {} + + /** + * Serializes the specified object and returns the result as a byte array. + * @param obj the object to serialize + * @return the serialized object + */ + public static byte[] serialize(final Serializable obj) { + return serialize(new Serializable[] {obj}); + } + + /** + * Serializes the specified object and returns the result as a byte array. + * @param objs an array of objects to serialize + * @return the serialized object + */ + public static byte[] serialize(final Serializable... objs) { + try { + final ByteArrayOutputStream bas = new ByteArrayOutputStream(8192); + final ObjectOutput oos = new ObjectOutputStream(bas); + for (final Object obj : objs) { + oos.writeObject(obj); + } + oos.flush(); + return bas.toByteArray(); + } catch (final Exception ex) { + throw new IllegalStateException("Could not serialize", ex); + } + } + + /** + * Deserialize an object from the specified byte array and returns the result. + * @param data byte array representing the serialized object + * @return the deserialized object + */ + @SuppressWarnings("unchecked") + @SuppressFBWarnings("OBJECT_DESERIALIZATION") + public static T deserialize(final byte[] data) { + try { + final ObjectInputStream ois = getObjectInputStream(data); + return (T) ois.readObject(); + } catch (final Exception ex) { + throw new IllegalStateException("Could not deserialize", ex); + } + } + + /** + * Creates an {@link ObjectInputStream} adapted to the current Java version. + * @param data data to deserialize, + * @return an object input stream. + */ + @SuppressFBWarnings("OBJECT_DESERIALIZATION") + public static ObjectInputStream getObjectInputStream(final byte[] data) throws IOException { + final ByteArrayInputStream bas = new ByteArrayInputStream(data); + return getObjectInputStream(bas); + } + + /** + * Creates an {@link ObjectInputStream} adapted to the current Java version. + * @param stream stream of data to deserialize, + * @return an object input stream. + */ + @SuppressFBWarnings("OBJECT_DESERIALIZATION") + public static ObjectInputStream getObjectInputStream(final InputStream stream) throws IOException { + return Constants.JAVA_MAJOR_VERSION == 8 + ? new FilteredObjectInputStream(stream) + : new ObjectInputStream(stream); + } +} 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..9989235165c --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SetTestProperty.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.test.junit; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +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; + +/** + * 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, TestPropertyResolver.class}) +@Repeatable(SetTestProperty.SetTestProperties.class) +@ReadsSystemProperty +@ReadsEnvironmentVariable +public @interface SetTestProperty { + + String key(); + + String value(); + + @Retention(RUNTIME) + @Target({TYPE, METHOD}) + @Documented + @Inherited + @interface SetTestProperties { + + SetTestProperty[] value(); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.java new file mode 100644 index 00000000000..7ca5776c583 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusListenerExtension.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.test.junit; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.status.StatusConsoleListener; +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.ListStatusListener; +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.jupiter.api.extension.ExtensionContextException; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; + +class StatusListenerExtension extends TypeBasedParameterResolver + implements BeforeAllCallback, BeforeEachCallback, TestExecutionExceptionHandler { + + private static final Object KEY = ListStatusListener.class; + + public StatusListenerExtension() { + super(ListStatusListener.class); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + // Stores the per-class status listener to catch the messages caused by other + // `beforeAll` methods and extensions. + final ListStatusListenerHolder holder = new ListStatusListenerHolder(context, null); + ExtensionContextAnchor.setAttribute(KEY, holder, context); + ReflectionSupport.findFields( + context.getRequiredTestClass(), + f -> ModifierSupport.isStatic(f) && f.getType().equals(ListStatusListener.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(null, holder.getStatusListener()); + } catch (final ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field.", e); + } + }); + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + // Retrieves the per-class status listener + final ListStatusListenerHolder parentHolder = + ExtensionContextAnchor.getAttribute(KEY, ListStatusListenerHolder.class, context); + final ListStatusListener parent = parentHolder != null ? parentHolder.getStatusListener() : null; + final ListStatusListenerHolder holder = new ListStatusListenerHolder(context, parent); + ExtensionContextAnchor.setAttribute(KEY, holder, context); + ReflectionSupport.findFields( + context.getRequiredTestClass(), + f -> ModifierSupport.isNotStatic(f) && f.getType().equals(ListStatusListener.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(context.getRequiredTestInstance(), holder.getStatusListener()); + } catch (final ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field.", e); + } + }); + } + + @Override + public void handleTestExecutionException(final ExtensionContext context, final Throwable throwable) + throws Throwable { + final ListStatusListenerHolder holder = + ExtensionContextAnchor.getAttribute(KEY, ListStatusListenerHolder.class, context); + if (holder != null) { + holder.handleException(context, throwable); + } + throw throwable; + } + + @Override + public ListStatusListener resolveParameter( + final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final ListStatusListenerHolder holder = + ExtensionContextAnchor.getAttribute(KEY, ListStatusListenerHolder.class, extensionContext); + return holder.getStatusListener(); + } + + private static class ListStatusListenerHolder implements CloseableResource { + + private final StatusLogger statusLogger; + private final ListStatusListener statusListener; + + public ListStatusListenerHolder(final ExtensionContext context, final ListStatusListener parent) { + this.statusLogger = StatusLogger.getLogger(); + this.statusListener = new JUnitListStatusListener(context, parent); + statusLogger.registerListener(statusListener); + } + + public ListStatusListener getStatusListener() { + return statusListener; + } + + @Override + public void close() { + statusLogger.removeListener(statusListener); + } + + public void handleException(final ExtensionContext context, final Throwable throwable) { + final StatusListener listener = new StatusConsoleListener(Level.ALL, System.err); + listener.log(new StatusData( + null, + Level.ERROR, + new ParameterizedMessage("Test `{}` has failed, dumping status data...", context.getDisplayName()), + throwable, + null)); + statusListener.getStatusData().forEach(listener::log); + } + } + + private static class JUnitListStatusListener implements ListStatusListener { + + private final ExtensionContext context; + private final ListStatusListener parent; + private final ArrayList statusData = new ArrayList<>(); + + public JUnitListStatusListener(final ExtensionContext context, final ListStatusListener parent) { + this.context = context; + this.parent = parent; + } + + @Override + public void log(final StatusData data) { + if (context.equals(ExtensionContextAnchor.getContext())) { + synchronized (statusData) { + statusData.add(data); + } + } + } + + @Override + public Level getStatusLevel() { + return Level.DEBUG; + } + + @Override + public void close() { + // NOP + } + + @Override + public Stream getStatusData() { + synchronized (statusData) { + final List clone = new ArrayList<>(statusData); + return parent != null ? Stream.concat(parent.getStatusData(), clone.stream()) : clone.stream(); + } + } + + @Override + public void clear() { + synchronized (statusData) { + statusData.clear(); + } + } + } +} 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..ffeeee07a99 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevel.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.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * 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..54534ed9dca --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevelExtension.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.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(final 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(final ExtensionContext context) throws Exception { + StatusLogger.getLogger().setLevel(getStore(context).get(KEY, Level.class)); + } + + private ExtensionContext.Store getStore(final ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance())); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.java new file mode 100644 index 00000000000..2725dc7314a --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerMockExtension.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.test.junit; + +import static org.apache.logging.log4j.test.junit.ExtensionContextAnchor.getAttribute; +import static org.apache.logging.log4j.test.junit.ExtensionContextAnchor.setAttribute; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import org.apache.logging.log4j.status.StatusConsoleListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Replaces {@link StatusLogger} static instance with a mocked one. + *

+ * Warning! + * Many classes store the result of {@link StatusLogger#getLogger()} in {@code static} field. + * Hence, the mock replacement must be performed before anybody tries to access it. + * Similarly, we cannot replace the mock in between tests, since it is already stored in {@code static} fields. + * That is why we only reset the mocked instance before each test. + *

+ * + * @see UsingStatusLoggerMock + */ +class StatusLoggerMockExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback { + + private static final String KEY_PREFIX = StatusLoggerMockExtension.class.getSimpleName() + '.'; + + private static final String INITIAL_STATUS_LOGGER_KEY = KEY_PREFIX + "initialStatusLogger"; + + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + setAttribute(INITIAL_STATUS_LOGGER_KEY, StatusLogger.getLogger(), context); + final StatusLogger statusLogger = mock(StatusLogger.class); + stubFallbackListener(statusLogger); + StatusLogger.setLogger(statusLogger); + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + final StatusLogger statusLogger = StatusLogger.getLogger(); + reset(statusLogger); // Stubs get reset too! + stubFallbackListener(statusLogger); + } + + private static void stubFallbackListener(final StatusLogger statusLogger) { + final StatusConsoleListener fallbackListener = mock(StatusConsoleListener.class); + when(statusLogger.getFallbackListener()).thenReturn(fallbackListener); + } + + @Override + public void afterAll(final ExtensionContext context) { + final StatusLogger statusLogger = getAttribute(INITIAL_STATUS_LOGGER_KEY, StatusLogger.class, context); + StatusLogger.setLogger(statusLogger); + } +} 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 76% 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..2e808adca13 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 @@ -1,20 +1,20 @@ /* * 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.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/TempLoggingDir.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TempLoggingDir.java new file mode 100644 index 00000000000..dab49ddd4d2 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TempLoggingDir.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 static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.CleanupMode; + +/** + * Injects the given static field with a per test logging directory. + *

+ * The same directory is set as "logging.path" Log4j2 property. + *

+ */ +@Retention(RUNTIME) +@Target({FIELD, PARAMETER}) +@Inherited +@Documented +@ExtendWith({ExtensionContextAnchor.class, TempLoggingDirectory.class}) +public @interface TempLoggingDir { + + CleanupMode cleanup() default CleanupMode.DEFAULT; +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TempLoggingDirectory.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TempLoggingDirectory.java new file mode 100644 index 00000000000..1ba5a16442e --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TempLoggingDirectory.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.test.junit; + +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.TestProperties; +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.jupiter.api.extension.ExtensionContextException; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.ModifierSupport; + +public class TempLoggingDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + final List fields = AnnotationSupport.findAnnotatedFields( + context.getRequiredTestClass(), TempLoggingDir.class, ModifierSupport::isStatic); + Path loggingPath = null; + for (final Field field : fields) { + if (loggingPath != null) { + throw new PreconditionViolationException( + "Multiple static fields with @TempLoggingDir annotation are not supported."); + } else { + final CleanupMode cleanup = determineCleanupMode(field); + loggingPath = createLoggingPath(context, cleanup).getPath(); + } + field.setAccessible(true); + field.set(null, loggingPath); + } + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + // JUnit 5 does not set an error on the parent context if one of the children fails. + // We record the list of children. + context.getParent().ifPresent(c -> { + final PathHolder holder = ExtensionContextAnchor.getAttribute(PathHolder.class, PathHolder.class, c); + if (holder != null) { + holder.addContext(context); + } + }); + // Inject fields + final List fields = AnnotationSupport.findAnnotatedFields( + context.getRequiredTestClass(), TempLoggingDir.class, ModifierSupport::isNotStatic); + Path loggingPath = null; + final Object instance = context.getRequiredTestInstance(); + for (final Field field : fields) { + if (loggingPath != null) { + throw new PreconditionViolationException( + "Multiple instance fields with @TempLoggingDir annotation are not supported."); + } else { + final CleanupMode cleanup = determineCleanupMode(field); + loggingPath = createLoggingPath(context, cleanup).getPath(); + } + field.setAccessible(true); + field.set(instance, loggingPath); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + if (parameterContext.getParameter().getType().isAssignableFrom(Path.class)) { + return parameterContext.findAnnotation(TempLoggingDir.class).isPresent(); + } + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + final TempLoggingDir annotation = parameterContext + .findAnnotation(TempLoggingDir.class) + .orElseThrow(() -> new PreconditionViolationException(String.format( + "Missing `%s` annotation on parameter `%s`", + TempLoggingDir.class.getSimpleName(), parameterContext))); + // Get or create a temporary directory + PathHolder holder = ExtensionContextAnchor.getAttribute(PathHolder.class, PathHolder.class, extensionContext); + if (holder == null || !extensionContext.equals(holder.getMainContext())) { + final CleanupMode mode = determineCleanupMode(annotation); + holder = createLoggingPath(extensionContext, mode); + } + return holder.getPath(); + } + + private PathHolder createLoggingPath(ExtensionContext context, CleanupMode cleanup) { + final TestProperties props = TestPropertySource.createProperties(context); + final Path perClassPath = determinePerClassPath(context); + // Per test subfolder + final Path loggingPath = context.getTestMethod() + .map(m -> perClassPath.resolve(m.getName())) + .orElse(perClassPath); + try { + Files.createDirectories(loggingPath); + } catch (final IOException e) { + throw new ExtensionContextException("Failed to create temporary directory.", e); + } + props.setProperty(TestProperties.LOGGING_PATH, loggingPath.toString()); + // Register deletion + final PathHolder holder = new PathHolder(loggingPath, cleanup, context); + ExtensionContextAnchor.setAttribute(PathHolder.class, holder, context); + return holder; + } + + private Path determinePerClassPath(ExtensionContext context) { + // Check if the parent context already created a folder + PathHolder holder = ExtensionContextAnchor.getAttribute(PathHolder.class, PathHolder.class, context); + if (holder == null) { + try { + // Create temporary per-class directory + final String baseDir = System.getProperty("basedir"); + final Path basePath = (baseDir != null ? Paths.get(baseDir, "target") : Paths.get(".")).resolve("logs"); + final Class clazz = context.getRequiredTestClass(); + final Package pkg = clazz.getPackage(); + final String dir = pkg.getName() + .replaceAll("org\\.apache\\.(logging\\.)?log4j\\.", "") + .replaceAll("[.$]", File.separatorChar == '\\' ? "\\\\" : File.separator); + // Create a temporary directory that uses the simple class name as prefix + Path packagePath = basePath.resolve(dir); + Files.createDirectories(packagePath); + // Use a UNIX timestamp to (roughly) sort directories by execution time. + return Files.createTempDirectory( + packagePath, + String.format("%s_%08x_", clazz.getSimpleName(), System.currentTimeMillis() / 1000)); + } catch (final IOException e) { + throw new ExtensionContextException("Failed to create temporary directory.", e); + } + } + return holder.getPath(); + } + + private CleanupMode determineCleanupMode(final TempLoggingDir annotation) { + final CleanupMode mode = annotation.cleanup(); + // TODO: use JupiterConfiguration + return mode != CleanupMode.DEFAULT ? mode : CleanupMode.ON_SUCCESS; + } + + private CleanupMode determineCleanupMode(final Field field) { + return determineCleanupMode(field.getAnnotation(TempLoggingDir.class)); + } + + private static class PathHolder implements CloseableResource { + + private final Path path; + private final CleanupMode cleanupMode; + private final ExtensionContext mainContext; + private final Map contexts = new ConcurrentHashMap<>(); + + public PathHolder(final Path path, final CleanupMode cleanup, final ExtensionContext context) { + this.path = path; + this.cleanupMode = cleanup; + this.contexts.put(context, Boolean.TRUE); + this.mainContext = context; + } + + public void addContext(final ExtensionContext context) { + this.contexts.put(context, Boolean.TRUE); + } + + public Path getPath() { + return path; + } + + public ExtensionContext getMainContext() { + return mainContext; + } + + @Override + public void close() throws IOException { + if (cleanupMode == NEVER + || (cleanupMode == ON_SUCCESS + && contexts.keySet().stream().anyMatch(context -> context.getExecutionException() + .isPresent()))) { + StatusLogger.getLogger().debug("Skipping cleanup of directory {}.", path); + return; + } + DirectoryCleaner.deleteDirectory(path); + } + } +} 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..93af5c29122 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertyResolver.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.test.junit; + +import org.apache.logging.log4j.test.TestProperties; +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.AnnotationSupport; +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(final ExtensionContext context) throws Exception { + final TestProperties props = TestPropertySource.createProperties(context); + AnnotationSupport.findRepeatableAnnotations(context.getRequiredTestMethod(), SetTestProperty.class) + .forEach(setProperty -> props.setProperty(setProperty.key(), setProperty.value())); + final Class testClass = context.getRequiredTestClass(); + final Object testInstance = context.getRequiredTestInstance(); + ReflectionSupport.findFields( + testClass, + field -> ModifierSupport.isNotStatic(field) + && field.getType().equals(TestProperties.class), + HierarchyTraversalMode.BOTTOM_UP) + .forEach(field -> { + try { + field.setAccessible(true); + field.set(testInstance, props); + } catch (IllegalAccessException e) { + throw new UnsupportedOperationException(e); + } + }); + } + + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + final TestProperties props = TestPropertySource.createProperties(context); + AnnotationSupport.findRepeatableAnnotations(context.getRequiredTestClass(), SetTestProperty.class) + .forEach(setProperty -> props.setProperty(setProperty.key(), setProperty.value())); + final Class testClass = context.getRequiredTestClass(); + ReflectionSupport.findFields( + testClass, + field -> ModifierSupport.isStatic(field) + && field.getType().equals(TestProperties.class), + HierarchyTraversalMode.BOTTOM_UP) + .forEach(field -> { + try { + field.setAccessible(true); + field.set(null, props); + } catch (IllegalAccessException e) { + throw new UnsupportedOperationException(e); + } + }); + } + + @Override + public TestProperties resolveParameter( + final ParameterContext parameterContext, final 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..c6057223ba6 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertySource.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.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(final 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(final ExtensionContext context) { + final ExtensionContext actualContext = context != null ? context : ExtensionContextAnchor.getContext(); + if (actualContext != null) { + final TestProperties props = + ExtensionContextAnchor.getAttribute(TestProperties.class, TestProperties.class, actualContext); + if (props != null) { + return props; + } + } + return EMPTY_PROPERTIES; + } + + @Override + public CharSequence getNormalForm(final 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(final String key) { + return getProperties().getProperty(key); + } + + @Override + public boolean containsProperty(final String key) { + return getProperties().containsProperty(key); + } + + private static class JUnitTestProperties implements TestProperties { + + private final ExtensionContext context; + private final Store store; + + public JUnitTestProperties(final ExtensionContext context) { + this.context = context; + this.store = context.getStore(NAMESPACE); + } + + public ExtensionContext getContext() { + return context; + } + + @Override + public String getProperty(final String key) { + return store.get(key, String.class); + } + + @Override + public boolean containsProperty(final String key) { + return getProperty(key) != null; + } + + @Override + public void setProperty(final String key, final String value) { + store.put(key, value); + } + + @Override + public void clearProperty(final String key) { + store.remove(key, String.class); + } + } + + private static class EmptyTestProperties implements TestProperties { + + @Override + public String getProperty(final String key) { + return null; + } + + @Override + public boolean containsProperty(final String key) { + return false; + } + + @Override + public void setProperty(final String key, final String value) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearProperty(final String key) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextExtension.java new file mode 100644 index 00000000000..5dc22ed2857 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextExtension.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.test.junit; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.test.ThreadContextHolder; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +class ThreadContextExtension implements BeforeEachCallback, AfterEachCallback { + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + final ThreadContextHolder holder; + if (testClass.isAnnotationPresent(UsingAnyThreadContext.class)) { + holder = new ThreadContextHolder(true, true); + ThreadContext.clearAll(); + } else if (testClass.isAnnotationPresent(UsingThreadContextMap.class)) { + holder = new ThreadContextHolder(true, false); + ThreadContext.clearMap(); + } else if (testClass.isAnnotationPresent(UsingThreadContextStack.class)) { + holder = new ThreadContextHolder(false, true); + ThreadContext.clearStack(); + } else { + return; + } + getStore(context).put(ThreadContextHolder.class, holder); + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + final ThreadContextHolder holder = getStore(context).get(ThreadContextHolder.class, ThreadContextHolder.class); + if (holder != null) { + holder.restore(); + } + } + + private ExtensionContext.Store getStore(final ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance())); + } +} 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..8afceef7378 --- /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(final ExtensionContext context) throws Exception { + if (AnnotationSupport.isAnnotated(context.getRequiredTestClass(), InitializesThreadContext.class)) { + resetThreadContext(context); + } + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + if (AnnotationSupport.isAnnotated(context.getRequiredTestMethod(), InitializesThreadContext.class)) { + resetThreadContext(context); + } + } + + private void resetThreadContext(final 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..3afab2f9d48 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapExtension.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 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 final 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-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.java new file mode 100644 index 00000000000..94986a26e62 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.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; + +/** + * Restores the ThreadContext to it's initial map values after a JUnit test. + * + * Usage: + * + *
+ * @Rule
+ * public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule();
+ * 
+ * + * @deprecated use {@link UsingThreadContextMap} with JUnit 5 + */ +@Deprecated +public class ThreadContextMapRule extends ThreadContextRule { + + /** + * Constructs an initialized instance. + */ + public ThreadContextMapRule() { + super(true, false); + } +} 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 80% 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..d8c870781b4 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 @@ -1,35 +1,38 @@ /* * 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.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; /** * Restores the ThreadContext to it's initial map and stack values after a JUnit test. - * + * * Usage: - * + * *
  * @Rule
  * public final ThreadContextRule threadContextRule = new ThreadContextRule();
  * 
+ * + * @deprecated use {@link UsingAnyThreadContext} with JUnit 5 */ +@Deprecated public class ThreadContextRule extends ExternalResource { private final boolean restoreMap; @@ -45,14 +48,13 @@ public ThreadContextRule() { /** * Constructs an instance initialized to restore the given structures. - * + * * @param restoreMap * Whether to restore the thread context map. * @param restoreStack * Whether to restore the thread context stack. */ public ThreadContextRule(final boolean restoreMap, final boolean restoreStack) { - super(); this.restoreMap = restoreMap; this.restoreStack = restoreStack; } @@ -74,5 +76,4 @@ protected void before() throws Throwable { ThreadContext.clearStack(); } } - } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.java new file mode 100644 index 00000000000..452c322a52d --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.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.junit; + +/** + * Restores the ThreadContext to it's initial stack values after a JUnit test. + * + * Usage: + * + *
+ * @Rule
+ * public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule();
+ * 
+ * @deprecated use {@link UsingThreadContextStack} with JUnit 5 + */ +@Deprecated +public class ThreadContextStackRule extends ThreadContextRule { + + /** + * Constructs an initialized instance. + */ + public ThreadContextStackRule() { + super(false, true); + } +} 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..970c1aff73c --- /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(final Type supportedParameterType) { + this.supportedParameterType = supportedParameterType; + } + + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final 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/UsingAnyThreadContext.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingAnyThreadContext.java new file mode 100644 index 00000000000..c67a9a1994a --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingAnyThreadContext.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.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +/** + * 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) +@Documented +@Inherited +@ExtendWith(ThreadContextExtension.class) +@ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) +public @interface UsingAnyThreadContext {} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.java new file mode 100644 index 00000000000..7fa198c509b --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusListener.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.junit; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.test.ListStatusListener; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Configures and injects a {@link StatusListener} of type + * {@link ListStatusListener}, that will collect status messages for the test + * context. + */ +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +@Documented +@ExtendWith({ExtensionContextAnchor.class, TestPropertyResolver.class, StatusListenerExtension.class}) +public @interface UsingStatusListener {} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.java new file mode 100644 index 00000000000..bcada66f7c9 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingStatusLoggerMock.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.test.junit; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Shortcut to {@link StatusLoggerMockExtension}. + */ +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +@Documented +@ExtendWith({ExtensionContextAnchor.class, StatusLoggerMockExtension.class}) +@ResourceLock("log4j2.StatusLogger") +public @interface UsingStatusLoggerMock {} 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..6d66dd4e032 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingTestProperties.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 static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +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; + +/** + * 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, 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..5876af72aa6 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextMap.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.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; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +/** + * 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}) +@Documented +@Inherited +@ExtendWith(ThreadContextMapExtension.class) +@ReadsSystemProperty +@ResourceLock(value = Log4jStaticResources.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..309dc1ce642 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextStack.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.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +/** + * 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) +@Documented +@Inherited +@ExtendWith(ThreadContextExtension.class) +@ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) +public @interface UsingThreadContextStack {} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java new file mode 100644 index 00000000000..2a37b2933ff --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/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. + */ +@Export +@Version("2.24.1") +package org.apache.logging.log4j.test.junit; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java new file mode 100644 index 00000000000..5a0ebc45101 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/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. + */ +@Export +@Version("2.21.1") +package org.apache.logging.log4j.test; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java new file mode 100644 index 00000000000..23bb42d483a --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.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.test.spi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +/** + * Provides test cases to apply to all implementations of {@link ThreadContextMap}. + * @since 2.24.0 + */ +@Execution(ExecutionMode.CONCURRENT) +public abstract class ThreadContextMapSuite { + + private static final String KEY = "key"; + + /** + * Checks if the context map does not propagate to other threads by default. + */ + protected static void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) { + contextMap.put(KEY, "threadLocalNotInheritableByDefault"); + verifyThreadContextValueFromANewThread(contextMap, null); + } + + /** + * Checks if the context map can be configured to propagate to other threads. + */ + protected static void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) { + contextMap.put(KEY, "threadLocalInheritableIfConfigured"); + verifyThreadContextValueFromANewThread(contextMap, "threadLocalInheritableIfConfigured"); + } + + /** + * Checks basic put/remove pattern. + */ + protected static void singleValue(final ThreadContextMap contextMap) { + assertThat(contextMap.isEmpty()).as("Map is empty").isTrue(); + contextMap.put(KEY, "testPut"); + assertThat(contextMap.isEmpty()).as("Map is not empty").isFalse(); + assertThat(contextMap.containsKey(KEY)).as("Map key exists").isTrue(); + assertThat(contextMap.get(KEY)).as("Map contains expected value").isEqualTo("testPut"); + contextMap.remove(KEY); + assertThat(contextMap.isEmpty()).as("Map is empty").isTrue(); + } + + /** + * Checks mutable copy + */ + protected static void getCopyReturnsMutableCopy(final ThreadContextMap contextMap) { + contextMap.put(KEY, "testGetCopyReturnsMutableCopy"); + + final Map copy = contextMap.getCopy(); + assertThat(copy).as("Copy contains same value").containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy")); + + copy.put(KEY, "testGetCopyReturnsMutableCopy2"); + assertThat(contextMap.get(KEY)) + .as("Original map is not affected by changes in the copy") + .isEqualTo("testGetCopyReturnsMutableCopy"); + + contextMap.clear(); + assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue(); + assertThat(copy) + .as("Copy is not affected by changes in the map.") + .containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy2")); + } + + /** + * The immutable copy must be {@code null} if the map is empty. + */ + protected static void getImmutableMapReturnsNullIfEmpty(final ThreadContextMap contextMap) { + assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue(); + assertThat(contextMap.getImmutableMapOrNull()) + .as("Immutable copy is null") + .isNull(); + } + + /** + * The result of {@link ThreadContextMap#getImmutableMapOrNull()} must be immutable. + */ + protected static void getImmutableMapReturnsImmutableMapIfNonEmpty(final ThreadContextMap contextMap) { + contextMap.put(KEY, "getImmutableMapReturnsImmutableMapIfNonEmpty"); + + final Map immutable = contextMap.getImmutableMapOrNull(); + assertThat(immutable) + .as("Immutable copy contains same value") + .containsExactly(entry(KEY, "getImmutableMapReturnsImmutableMapIfNonEmpty")); + + assertThrows( + UnsupportedOperationException.class, () -> immutable.put(KEY, "getImmutableMapReturnsNullIfEmpty2")); + } + + /** + * The immutable copy is not affected by changes to the original map. + */ + protected static void getImmutableMapCopyNotAffectedByContextMapChanges(final ThreadContextMap contextMap) { + contextMap.put(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges"); + + final Map immutable = contextMap.getImmutableMapOrNull(); + contextMap.put(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges2"); + assertThat(immutable) + .as("Immutable copy contains the original value") + .containsExactly(entry(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges")); + } + + private static void verifyThreadContextValueFromANewThread( + final ThreadContextMap contextMap, final String expected) { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + assertThat(executorService.submit(() -> contextMap.get(KEY))) + .succeedsWithin(Duration.ofSeconds(1)) + .isEqualTo(expected); + } finally { + executorService.shutdown(); + } + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/OsgiServiceLocatorTest.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/OsgiServiceLocatorTest.java new file mode 100644 index 00000000000..7c18f9003b7 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/OsgiServiceLocatorTest.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.util; + +import java.lang.invoke.MethodHandles; +import java.util.stream.Stream; +import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.util.OsgiServiceLocator; +import org.apache.logging.log4j.util.PropertySource; + +public class OsgiServiceLocatorTest { + + /** + * Used by OSGI {@link AbstractLoadBundleTest} to preserve caller + * sensitivity. + * + * @return + */ + public static Stream loadProviders() { + return OsgiServiceLocator.loadServices(Provider.class, MethodHandles.lookup()); + } + + public static Stream loadPropertySources() { + return OsgiServiceLocator.loadServices(PropertySource.class, MethodHandles.lookup()); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/package-info.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/package-info.java new file mode 100644 index 00000000000..949af1b0527 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/util/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.test.util; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api/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 similarity index 87% rename from log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource rename to log4j-api-test/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource index 39c959ca65a..2c7cb498d92 100644 --- a/log4j-api/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 @@ -12,5 +12,4 @@ # WITHOUT 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.EnvironmentPropertySource -org.apache.logging.log4j.util.SystemPropertiesPropertySource \ No newline at end of file +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/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..79a8fd75dd8 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java @@ -0,0 +1,1459 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.List; +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.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.Log4jStaticResources; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.apache.logging.log4j.util.Constants; +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 org.junitpioneer.jupiter.SetSystemProperty; + +@StatusLoggerLevel("WARN") +@ResourceLock(value = Log4jStaticResources.MARKER_MANAGER, mode = ResourceAccessMode.READ) +@SetSystemProperty(key = "log4j2.status.entries", value = "200") +@SetSystemProperty(key = "log4j2.StatusLogger.level", value = "WARN") +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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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") + void testMessageThrows() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + logger.error(new TestMessage( + () -> { + throw new IllegalStateException("Oops!"); + }, + "Message Format")); + final List statusDatalist = StatusLogger.getLogger().getStatusData(); + final 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") + void testMessageThrowsAndNullFormat() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + logger.error(new TestMessage( + () -> { + throw new IllegalStateException("Oops!"); + }, + null /* format */)); + final List statusDatalist = StatusLogger.getLogger().getStatusData(); + final 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 final FormattedMessageSupplier formattedMessageSupplier; + private final String format; + + TestMessage(final FormattedMessageSupplier formattedMessageSupplier, final 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 Constants.EMPTY_OBJECT_ARRAY; + } + + @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", 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) { + 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", 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(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 Object[] getParameters() { + return Constants.EMPTY_OBJECT_ARRAY; + } + + @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..c6bab5da531 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +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; + +/** + * Tests {@link CloseableThreadContext}. + * + * @since 2.6 + */ +@ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) +class CloseableThreadContextTest { + + private final String key = "key"; + private final String value = "value"; + + @BeforeEach + @AfterEach + void clearThreadContext() { + ThreadContext.clearAll(); + } + + @Test + void shouldAddAnEntryToTheMap() { + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + } + + @Test + 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 + 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 + 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 + 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 + void shouldPushAndPopAnEntryToTheStack() { + final String message = "message"; + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(message)) { + assertNotNull(ignored); + assertEquals(message, ThreadContext.peek()); + } + assertEquals("", ThreadContext.peek()); + } + + @Test + 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 + 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 + void shouldRemoveAnEntryFromTheMapWhenAutoClosed() { + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + assertFalse(ThreadContext.containsKey(key)); + } + + @Test + 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 + 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 + 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 + 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 + 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()); + } + + /** + * User provided test stressing nesting using {@link CloseableThreadContext#put(String, String)}. + * + * @see #2946 + */ + @Test + void testAutoCloseableThreadContextPut() { + try (final CloseableThreadContext.Instance ctc1 = CloseableThreadContext.put("outer", "one")) { + try (final CloseableThreadContext.Instance ctc2 = CloseableThreadContext.put("outer", "two")) { + assertEquals("two", ThreadContext.get("outer")); + + try (final CloseableThreadContext.Instance ctc3 = CloseableThreadContext.put("inner", "one")) { + assertEquals("one", ThreadContext.get("inner")); + + ThreadContext.put( + "not-in-closeable", "true"); // Remove this line, and closing context behaves as expected + assertEquals("two", ThreadContext.get("outer")); + } + + assertEquals("two", ThreadContext.get("outer")); + assertNull(ThreadContext.get("inner")); // Test fails here + } + + assertEquals("one", ThreadContext.get("outer")); + assertNull(ThreadContext.get("inner")); + } + assertEquals("true", ThreadContext.get("not-in-closeable")); + + assertNull(ThreadContext.get("inner")); + assertNull(ThreadContext.get("outer")); + } + + /** + * User provided test stressing nesting using {@link CloseableThreadContext#putAll(Map)}. + * + * @see #2946 + */ + @Test + void testAutoCloseableThreadContextPutAll() { + try (final CloseableThreadContext.Instance ctc1 = CloseableThreadContext.put("outer", "one")) { + try (final CloseableThreadContext.Instance ctc2 = CloseableThreadContext.put("outer", "two")) { + assertEquals("two", ThreadContext.get("outer")); + + try (final CloseableThreadContext.Instance ctc3 = CloseableThreadContext.put("inner", "one")) { + assertEquals("one", ThreadContext.get("inner")); + + ThreadContext.put( + "not-in-closeable", "true"); // Remove this line, and closing context behaves as expected + ThreadContext.putAll(Collections.singletonMap("inner", "two")); // But this is not a problem + assertEquals("two", ThreadContext.get("inner")); + assertEquals("two", ThreadContext.get("outer")); + } + + assertEquals("two", ThreadContext.get("outer")); + assertNull(ThreadContext.get("inner")); // This is where the test fails + } + + assertEquals("one", ThreadContext.get("outer")); + assertNull(ThreadContext.get("inner")); + } + assertEquals("true", ThreadContext.get("not-in-closeable")); + + assertNull(ThreadContext.get("inner")); + assertNull(ThreadContext.get("outer")); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.java new file mode 100644 index 00000000000..a6611bbf27f --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Locale; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.test.TestLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +@ResourceLock("log4j2.TestLogger") +class EventLoggerTest { + + TestLogger logger = (TestLogger) LogManager.getLogger("EventLogger"); + List results = logger.getEntries(); + + @BeforeEach + void setup() { + results.clear(); + } + + @Test + void structuredData() { + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName(Locale.US)); + final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + EventLogger.logEvent(msg); + ThreadContext.clearMap(); + assertThat(results).hasSize(1); + final String expected = + "EVENT OFF Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete"; + 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 84% 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..c84ba45e91b 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 @@ -1,52 +1,56 @@ /* * 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.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.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + 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 static org.junit.Assert.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * Tests the AbstractLogger implementation of the Logger2 interface. */ -public class LambdaLoggerTest { +class LambdaLoggerTest { private static class LogEvent { @SuppressWarnings("unused") final String fqcn; + final Level level; final Marker marker; final Message message; final Throwable throwable; - public LogEvent(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { + public LogEvent( + final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { this.fqcn = fqcn; this.level = level; this.marker = marker; - this.message = message; + this.message = (message instanceof ReusableMessage) ? ((ReusableMessage) message).memento() : message; this.throwable = t; } } @@ -63,7 +67,8 @@ public boolean isEnabled(final Level level, final Marker marker, final Message m } @Override - public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { + public boolean isEnabled( + final Level level, final Marker marker, final CharSequence message, final Throwable t) { return enabled; } @@ -93,70 +98,130 @@ public boolean isEnabled(final Level level, final Marker marker, final String me } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1) { + public boolean isEnabled( + final Level level, final Marker marker, final String message, final Object p0, final Object p1) { return enabled; } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { return enabled; } @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) { + 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 enabled; } @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, + 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 enabled; } @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) { + 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 enabled; } @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) { + 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 enabled; } @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, + 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 enabled; } @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) { + 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 enabled; } @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) { + 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 enabled; } @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { + public void logMessage( + final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { list.add(new LogEvent(fqcn, level, marker, message, t)); } @@ -209,15 +274,15 @@ public String get() { final Supplier[] supplierArray1 = new Supplier[] {supplier}; final Supplier[] supplierArray2 = new Supplier[] {supplier, supplier2}; - @Before - public void beforeEachTest() { + @BeforeEach + void beforeEachTest() { logger2.list.clear(); supplier.invoked = false; messageSupplier.invoked = false; } @Test - public void testDebugMarkerMessageSupplier() { + void testDebugMarkerMessageSupplier() { logger2.disable().debug(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -233,7 +298,7 @@ public void testDebugMarkerMessageSupplier() { } @Test - public void testDebugMessageSupplier() { + void testDebugMessageSupplier() { logger2.disable().debug(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -248,7 +313,7 @@ public void testDebugMessageSupplier() { } @Test - public void testDebugMarkerMessageSupplierThrowable() { + void testDebugMarkerMessageSupplierThrowable() { logger2.disable().debug(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -265,7 +330,7 @@ public void testDebugMarkerMessageSupplierThrowable() { } @Test - public void testDebugMessageSupplierThrowable() { + void testDebugMessageSupplierThrowable() { logger2.disable().debug(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -281,7 +346,7 @@ public void testDebugMessageSupplierThrowable() { } @Test - public void testDebugMarkerSupplier() { + void testDebugMarkerSupplier() { logger2.disable().debug(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -297,7 +362,7 @@ public void testDebugMarkerSupplier() { } @Test - public void testDebugSupplier() { + void testDebugSupplier() { logger2.disable().debug(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -312,7 +377,7 @@ public void testDebugSupplier() { } @Test - public void testDebugMarkerSupplierThrowable() { + void testDebugMarkerSupplierThrowable() { logger2.disable().debug(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -329,7 +394,7 @@ public void testDebugMarkerSupplierThrowable() { } @Test - public void testDebugSupplierThrowable() { + void testDebugSupplierThrowable() { logger2.disable().debug(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -345,7 +410,7 @@ public void testDebugSupplierThrowable() { } @Test - public void testDebugStringParamSupplier() { + void testDebugStringParamSupplier() { logger2.disable().debug("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -360,7 +425,7 @@ public void testDebugStringParamSupplier() { } @Test - public void testDebugMarkerStringParamSupplier() { + void testDebugMarkerStringParamSupplier() { logger2.disable().debug(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -376,7 +441,7 @@ public void testDebugMarkerStringParamSupplier() { } @Test - public void testErrorMarkerMessageSupplier() { + void testErrorMarkerMessageSupplier() { logger2.disable().error(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -392,7 +457,7 @@ public void testErrorMarkerMessageSupplier() { } @Test - public void testErrorMessageSupplier() { + void testErrorMessageSupplier() { logger2.disable().error(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -407,7 +472,7 @@ public void testErrorMessageSupplier() { } @Test - public void testErrorMarkerMessageSupplierThrowable() { + void testErrorMarkerMessageSupplierThrowable() { logger2.disable().error(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -424,7 +489,7 @@ public void testErrorMarkerMessageSupplierThrowable() { } @Test - public void testErrorMessageSupplierThrowable() { + void testErrorMessageSupplierThrowable() { logger2.disable().error(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -440,7 +505,7 @@ public void testErrorMessageSupplierThrowable() { } @Test - public void testErrorMarkerSupplier() { + void testErrorMarkerSupplier() { logger2.disable().error(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -456,7 +521,7 @@ public void testErrorMarkerSupplier() { } @Test - public void testErrorSupplier() { + void testErrorSupplier() { logger2.disable().error(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -471,7 +536,7 @@ public void testErrorSupplier() { } @Test - public void testErrorMarkerSupplierThrowable() { + void testErrorMarkerSupplierThrowable() { logger2.disable().error(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -488,7 +553,7 @@ public void testErrorMarkerSupplierThrowable() { } @Test - public void testErrorSupplierThrowable() { + void testErrorSupplierThrowable() { logger2.disable().error(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -504,7 +569,7 @@ public void testErrorSupplierThrowable() { } @Test - public void testErrorStringParamSupplier() { + void testErrorStringParamSupplier() { logger2.disable().error("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -519,7 +584,7 @@ public void testErrorStringParamSupplier() { } @Test - public void testErrorMarkerStringParamSupplier() { + void testErrorMarkerStringParamSupplier() { logger2.disable().error(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -535,7 +600,7 @@ public void testErrorMarkerStringParamSupplier() { } @Test - public void testFatalMarkerMessageSupplier() { + void testFatalMarkerMessageSupplier() { logger2.disable().fatal(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -551,7 +616,7 @@ public void testFatalMarkerMessageSupplier() { } @Test - public void testFatalMessageSupplier() { + void testFatalMessageSupplier() { logger2.disable().fatal(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -566,7 +631,7 @@ public void testFatalMessageSupplier() { } @Test - public void testFatalMarkerMessageSupplierThrowable() { + void testFatalMarkerMessageSupplierThrowable() { logger2.disable().fatal(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -583,7 +648,7 @@ public void testFatalMarkerMessageSupplierThrowable() { } @Test - public void testFatalMessageSupplierThrowable() { + void testFatalMessageSupplierThrowable() { logger2.disable().fatal(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -599,7 +664,7 @@ public void testFatalMessageSupplierThrowable() { } @Test - public void testFatalMarkerSupplier() { + void testFatalMarkerSupplier() { logger2.disable().fatal(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -615,7 +680,7 @@ public void testFatalMarkerSupplier() { } @Test - public void testFatalSupplier() { + void testFatalSupplier() { logger2.disable().fatal(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -630,7 +695,7 @@ public void testFatalSupplier() { } @Test - public void testFatalMarkerSupplierThrowable() { + void testFatalMarkerSupplierThrowable() { logger2.disable().fatal(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -647,7 +712,7 @@ public void testFatalMarkerSupplierThrowable() { } @Test - public void testFatalSupplierThrowable() { + void testFatalSupplierThrowable() { logger2.disable().fatal(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -663,7 +728,7 @@ public void testFatalSupplierThrowable() { } @Test - public void testFatalStringParamSupplier() { + void testFatalStringParamSupplier() { logger2.disable().fatal("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -678,7 +743,7 @@ public void testFatalStringParamSupplier() { } @Test - public void testFatalStringParam2Suppliers() { + void testFatalStringParam2Suppliers() { logger2.disable().fatal("abc {}{}", supplierArray2); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -695,7 +760,7 @@ public void testFatalStringParam2Suppliers() { } @Test - public void testFatalMarkerStringParamSupplier() { + void testFatalMarkerStringParamSupplier() { logger2.disable().fatal(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -711,7 +776,7 @@ public void testFatalMarkerStringParamSupplier() { } @Test - public void testInfoMarkerMessageSupplier() { + void testInfoMarkerMessageSupplier() { logger2.disable().info(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -727,7 +792,7 @@ public void testInfoMarkerMessageSupplier() { } @Test - public void testInfoMessageSupplier() { + void testInfoMessageSupplier() { logger2.disable().info(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -742,7 +807,7 @@ public void testInfoMessageSupplier() { } @Test - public void testInfoMarkerMessageSupplierThrowable() { + void testInfoMarkerMessageSupplierThrowable() { logger2.disable().info(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -759,7 +824,7 @@ public void testInfoMarkerMessageSupplierThrowable() { } @Test - public void testInfoMessageSupplierThrowable() { + void testInfoMessageSupplierThrowable() { logger2.disable().info(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -775,7 +840,7 @@ public void testInfoMessageSupplierThrowable() { } @Test - public void testInfoMarkerSupplier() { + void testInfoMarkerSupplier() { logger2.disable().info(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -791,7 +856,7 @@ public void testInfoMarkerSupplier() { } @Test - public void testInfoSupplier() { + void testInfoSupplier() { logger2.disable().info(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -806,7 +871,7 @@ public void testInfoSupplier() { } @Test - public void testInfoMarkerSupplierThrowable() { + void testInfoMarkerSupplierThrowable() { logger2.disable().info(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -823,7 +888,7 @@ public void testInfoMarkerSupplierThrowable() { } @Test - public void testInfoSupplierThrowable() { + void testInfoSupplierThrowable() { logger2.disable().info(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -839,7 +904,7 @@ public void testInfoSupplierThrowable() { } @Test - public void testInfoStringParamSupplier() { + void testInfoStringParamSupplier() { logger2.disable().info("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -854,7 +919,7 @@ public void testInfoStringParamSupplier() { } @Test - public void testInfoMarkerStringParamSupplier() { + void testInfoMarkerStringParamSupplier() { logger2.disable().info(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -870,7 +935,7 @@ public void testInfoMarkerStringParamSupplier() { } @Test - public void testTraceMarkerMessageSupplier() { + void testTraceMarkerMessageSupplier() { logger2.disable().trace(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -886,7 +951,7 @@ public void testTraceMarkerMessageSupplier() { } @Test - public void testTraceMessageSupplier() { + void testTraceMessageSupplier() { logger2.disable().trace(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -901,7 +966,7 @@ public void testTraceMessageSupplier() { } @Test - public void testTraceMarkerMessageSupplierThrowable() { + void testTraceMarkerMessageSupplierThrowable() { logger2.disable().trace(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -918,7 +983,7 @@ public void testTraceMarkerMessageSupplierThrowable() { } @Test - public void testTraceMessageSupplierThrowable() { + void testTraceMessageSupplierThrowable() { logger2.disable().trace(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -934,7 +999,7 @@ public void testTraceMessageSupplierThrowable() { } @Test - public void testTraceMarkerSupplier() { + void testTraceMarkerSupplier() { logger2.disable().trace(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -950,7 +1015,7 @@ public void testTraceMarkerSupplier() { } @Test - public void testTraceSupplier() { + void testTraceSupplier() { logger2.disable().trace(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -965,7 +1030,7 @@ public void testTraceSupplier() { } @Test - public void testTraceMarkerSupplierThrowable() { + void testTraceMarkerSupplierThrowable() { logger2.disable().trace(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -982,7 +1047,7 @@ public void testTraceMarkerSupplierThrowable() { } @Test - public void testTraceSupplierThrowable() { + void testTraceSupplierThrowable() { logger2.disable().trace(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -998,7 +1063,7 @@ public void testTraceSupplierThrowable() { } @Test - public void testTraceStringParamSupplier() { + void testTraceStringParamSupplier() { logger2.disable().trace("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1013,7 +1078,7 @@ public void testTraceStringParamSupplier() { } @Test - public void testTraceMarkerStringParamSupplier() { + void testTraceMarkerStringParamSupplier() { logger2.disable().trace(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1029,7 +1094,7 @@ public void testTraceMarkerStringParamSupplier() { } @Test - public void testWarnMarkerMessageSupplier() { + void testWarnMarkerMessageSupplier() { logger2.disable().warn(marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1045,7 +1110,7 @@ public void testWarnMarkerMessageSupplier() { } @Test - public void testWarnMessageSupplier() { + void testWarnMessageSupplier() { logger2.disable().warn(messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1060,7 +1125,7 @@ public void testWarnMessageSupplier() { } @Test - public void testWarnMarkerMessageSupplierThrowable() { + void testWarnMarkerMessageSupplierThrowable() { logger2.disable().warn(marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1077,7 +1142,7 @@ public void testWarnMarkerMessageSupplierThrowable() { } @Test - public void testWarnMessageSupplierThrowable() { + void testWarnMessageSupplierThrowable() { logger2.disable().warn(messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1093,7 +1158,7 @@ public void testWarnMessageSupplierThrowable() { } @Test - public void testWarnMarkerSupplier() { + void testWarnMarkerSupplier() { logger2.disable().warn(marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1109,7 +1174,7 @@ public void testWarnMarkerSupplier() { } @Test - public void testWarnSupplier() { + void testWarnSupplier() { logger2.disable().warn(supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1124,7 +1189,7 @@ public void testWarnSupplier() { } @Test - public void testWarnMarkerSupplierThrowable() { + void testWarnMarkerSupplierThrowable() { logger2.disable().warn(marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1141,7 +1206,7 @@ public void testWarnMarkerSupplierThrowable() { } @Test - public void testWarnSupplierThrowable() { + void testWarnSupplierThrowable() { logger2.disable().warn(supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1157,7 +1222,7 @@ public void testWarnSupplierThrowable() { } @Test - public void testWarnStringParamSupplier() { + void testWarnStringParamSupplier() { logger2.disable().warn("abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1172,7 +1237,7 @@ public void testWarnStringParamSupplier() { } @Test - public void testWarnMarkerStringParamSupplier() { + void testWarnMarkerStringParamSupplier() { logger2.disable().warn(marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1188,7 +1253,7 @@ public void testWarnMarkerStringParamSupplier() { } @Test - public void testLogMarkerMessageSupplier() { + void testLogMarkerMessageSupplier() { logger2.disable().log(Level.WARN, marker, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1204,7 +1269,7 @@ public void testLogMarkerMessageSupplier() { } @Test - public void testLogMessageSupplier() { + void testLogMessageSupplier() { logger2.disable().log(Level.WARN, messageSupplier); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1219,7 +1284,7 @@ public void testLogMessageSupplier() { } @Test - public void testLogMarkerMessageSupplierThrowable() { + void testLogMarkerMessageSupplierThrowable() { logger2.disable().log(Level.WARN, marker, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1236,7 +1301,7 @@ public void testLogMarkerMessageSupplierThrowable() { } @Test - public void testLogMessageSupplierThrowable() { + void testLogMessageSupplierThrowable() { logger2.disable().log(Level.WARN, messageSupplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(messageSupplier.invoked); @@ -1252,7 +1317,7 @@ public void testLogMessageSupplierThrowable() { } @Test - public void testLogMarkerSupplier() { + void testLogMarkerSupplier() { logger2.disable().log(Level.WARN, marker, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1268,7 +1333,7 @@ public void testLogMarkerSupplier() { } @Test - public void testLogSupplier() { + void testLogSupplier() { logger2.disable().log(Level.WARN, supplier); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1283,7 +1348,7 @@ public void testLogSupplier() { } @Test - public void testLogMarkerSupplierThrowable() { + void testLogMarkerSupplierThrowable() { logger2.disable().log(Level.WARN, marker, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1300,7 +1365,7 @@ public void testLogMarkerSupplierThrowable() { } @Test - public void testLogSupplierThrowable() { + void testLogSupplierThrowable() { logger2.disable().log(Level.WARN, supplier, throwable); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1316,7 +1381,7 @@ public void testLogSupplierThrowable() { } @Test - public void testLogStringParamSupplier() { + void testLogStringParamSupplier() { logger2.disable().log(Level.WARN, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1331,7 +1396,7 @@ public void testLogStringParamSupplier() { } @Test - public void testLogMarkerStringParamSupplier() { + void testLogMarkerStringParamSupplier() { logger2.disable().log(Level.WARN, marker, "abc {}", supplierArray1); assertTrue(logger2.list.isEmpty()); assertFalse(supplier.invoked); @@ -1345,5 +1410,4 @@ public void testLogMarkerStringParamSupplier() { assertSame(marker, event.marker); assertEquals("abc Hi", event.message.getFormattedMessage()); } - } 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..30f9e4073d5 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LevelTest.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +class LevelTest { + + @Test + void testDefault() { + final Level level = Level.toLevel("Information", Level.ERROR); + assertNotNull(level); + assertEquals(Level.ERROR, level); + } + + @Test + 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 + 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 + void testGoodLevels() { + final Level level = Level.toLevel("INFO"); + assertNotNull(level); + assertEquals(Level.INFO, level); + } + + @Test + 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 + 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 + 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 + 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 + 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 + 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..6d54a96c52a --- /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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +@ResourceLock(value = "log4j2.LoggerContextFactory", mode = ResourceAccessMode.READ) +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 { + static final Logger LOGGER = LogManager.getLogger(); + } + + static class StaticInnerByClass { + static final Logger LOGGER = LogManager.getLogger(StaticInnerByClass.class); + } + + @Test + 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 + void testGetLoggerForAnonymousInnerClass1() throws IOException { + final Closeable closeable = new Closeable() { + + final Logger LOGGER = LogManager.getLogger(); + + @Override + public void close() { + assertEquals("org.apache.logging.log4j.LogManagerTest$1", LOGGER.getName()); + } + }; + closeable.close(); + } + + @Test + void testGetLoggerForAnonymousInnerClass2() throws IOException { + final Closeable closeable = new Closeable() { + + final Logger LOGGER = LogManager.getLogger(getClass()); + + @Override + public void close() { + assertEquals("org.apache.logging.log4j.LogManagerTest$2", LOGGER.getName()); + } + }; + closeable.close(); + } + + @Test + void testGetLoggerForInner() { + assertEquals("org.apache.logging.log4j.LogManagerTest.Inner", new Inner().LOGGER.getName()); + } + + @Test + void testGetLoggerForInnerByClass() { + assertEquals("org.apache.logging.log4j.LogManagerTest.InnerByClass", new InnerByClass().LOGGER.getName()); + } + + @Test + void testGetLoggerForStaticInner() { + assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInner", StaticInner.LOGGER.getName()); + } + + @Test + void testGetLoggerForStaticInnerByClass() { + assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInnerByClass", StaticInnerByClass.LOGGER.getName()); + } + + @Test + 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..761425eabb2 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; + +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.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; + +/** + * Tests Logger APIs with {@link Supplier}. + */ +@ResourceLock(Resources.LOCALE) +@ResourceLock("log4j2.TestLogger") +class LoggerSupplierTest { + + private final TestLogger logger = (TestLogger) LogManager.getLogger("LoggerTest"); + + private final List results = logger.getEntries(); + + Locale defaultLocale; + + @Test + void flowTracing_SupplierOfFormattedMessage() { + logger.traceEntry(() -> new FormattedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("FormattedMessage"); + } + + @Test + void flowTracing_SupplierOfJsonMessage() { + final Properties props = new Properties(); + props.setProperty("foo", "bar"); + logger.traceEntry(() -> new JsonMessage(props)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("\"foo\":\"bar\"") + .doesNotContain("JsonMessage"); + } + + @Test + void flowTracing_SupplierOfLocalizedMessage() { + logger.traceEntry(() -> new LocalizedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("LocalizedMessage"); + } + + @Test + void flowTracing_SupplierOfLong() { + logger.traceEntry(() -> 1234567890L); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + void flowTracing_SupplierOfMessageFormatMessage() { + logger.traceEntry(() -> new MessageFormatMessage("int foo={0}", 1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1,234,567,890)") + .doesNotContain("MessageFormatMessage"); + } + + @Test + void flowTracing_SupplierOfObjectArrayMessage() { + logger.traceEntry(() -> new ObjectArrayMessage(1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("([1234567890])") + .doesNotContain("ObjectArrayMessage"); + } + + @Test + void flowTracing_SupplierOfObjectMessage() { + logger.traceEntry(() -> new ObjectMessage(1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("ObjectMessage"); + } + + @Test + void flowTracing_SupplierOfParameterizedMessage() { + logger.traceEntry(() -> new ParameterizedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("ParameterizedMessage"); + } + + @Test + void flowTracing_SupplierOfSimpleMessage() { + logger.traceEntry(() -> new SimpleMessage("1234567890")); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + void flowTracing_SupplierOfString() { + logger.traceEntry(() -> "1234567890"); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + void flowTracing_SupplierOfStringFormattedMessage() { + logger.traceEntry(() -> new StringFormattedMessage("int foo=%,d", 1234567890)); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1,234,567,890)") + .doesNotContain("StringFormattedMessage"); + } + + @Test + void flowTracing_SupplierOfThreadDumpMessage() { + logger.traceEntry(() -> new ThreadDumpMessage("Title of ...")); + assertThat(results).hasSize(1); + final String entry = results.get(0); + assertThat(entry) + .startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("RUNNABLE", "Title of ...", getClass().getName()); + } + + @BeforeEach + void setup() { + results.clear(); + defaultLocale = Locale.getDefault(Locale.Category.FORMAT); + Locale.setDefault(Locale.Category.FORMAT, java.util.Locale.US); + } + + @AfterEach + 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..ac00b97be9a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java @@ -0,0 +1,658 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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; + +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.spi.MessageFactory2Adapter; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.junit.Log4jStaticResources; +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; + +@ResourceLock(value = Log4jStaticResources.MARKER_MANAGER, mode = ResourceAccessMode.READ) +@ReadsSystemProperty +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 + 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 + void basicFlow() { + logger.entry(); + logger.exit(); + assertEquals(2, results.size()); + assertThat(results.get(0)).isEqualTo("ENTER[ FLOW ] TRACE Enter"); + assertThat(results.get(1)).isEqualTo("EXIT[ FLOW ] TRACE Exit"); + } + + @Test + void flowTracingMessage() { + final 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 + 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 + 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 + 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 + 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 + 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 + 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 + void flowTracingString_SupplierOfObjectMessages() { + final EntryMessage msg = logger.traceEntry( + "doFoo(a={}, b={})", (Supplier) () -> new ObjectMessage(1), (Supplier) + () -> 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 + void flowTracingString_SupplierOfStrings() { + final EntryMessage msg = + logger.traceEntry("doFoo(a={}, b={})", (Supplier) () -> "1", (Supplier) () -> "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 + 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 + 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 + void debug() { + logger.debug("Debug message"); + assertEquals(1, results.size()); + assertTrue(results.get(0).startsWith(" DEBUG Debug message"), "Incorrect message"); + } + + @Test + void debugObject() { + logger.debug(new Date()); + assertEquals(1, results.size()); + assertTrue(results.get(0).length() > 7, "Invalid length"); + } + + @Test + void debugWithParms() { + logger.debug("Hello, {}", "World"); + assertEquals(1, results.size()); + assertTrue(results.get(0).startsWith(" DEBUG Hello, World"), "Incorrect substitution"); + } + + @Test + 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(value = org.junit.jupiter.api.parallel.Resources.LOCALE, mode = ResourceAccessMode.READ) + 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(value = org.junit.jupiter.api.parallel.Resources.LOCALE, mode = ResourceAccessMode.READ) + 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 + @ResourceLock(value = org.junit.jupiter.api.parallel.Resources.LOCALE, mode = ResourceAccessMode.READ) + 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(value = org.junit.jupiter.api.parallel.Resources.LOCALE, mode = ResourceAccessMode.READ) + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + void getLoggerByClass() { + final Logger classLogger = LogManager.getLogger(LoggerTest.class); + assertNotNull(classLogger); + } + + @Test + void getLoggerByNullClass() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((Class) null)); + } + + @Test + void getLoggerByNullObject() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((Object) null)); + } + + @Test + void getLoggerByNullString() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((String) null)); + } + + @Test + void getLoggerByObject() { + final Logger classLogger = LogManager.getLogger(this); + assertNotNull(classLogger); + assertEquals(classLogger, LogManager.getLogger(LoggerTest.class)); + } + + @Test + 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 + void isAllEnabled() { + assertTrue(logger.isEnabled(Level.ALL), "Incorrect level"); + } + + @Test + void isDebugEnabled() { + assertTrue(logger.isDebugEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.DEBUG), "Incorrect level"); + } + + @Test + void isErrorEnabled() { + assertTrue(logger.isErrorEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.ERROR), "Incorrect level"); + } + + @Test + void isFatalEnabled() { + assertTrue(logger.isFatalEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.FATAL), "Incorrect level"); + } + + @Test + void isInfoEnabled() { + assertTrue(logger.isInfoEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.INFO), "Incorrect level"); + } + + @Test + void isOffEnabled() { + assertTrue(logger.isEnabled(Level.OFF), "Incorrect level"); + } + + @Test + void isTraceEnabled() { + assertTrue(logger.isTraceEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.TRACE), "Incorrect level"); + } + + @Test + void isWarnEnabled() { + assertTrue(logger.isWarnEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.WARN), "Incorrect level"); + } + + @Test + void isAllEnabledWithMarker() { + assertTrue(logger.isEnabled(Level.ALL, marker), "Incorrect level"); + } + + @Test + void isDebugEnabledWithMarker() { + assertTrue(logger.isDebugEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.DEBUG, marker), "Incorrect level"); + } + + @Test + void isErrorEnabledWithMarker() { + assertTrue(logger.isErrorEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.ERROR, marker), "Incorrect level"); + } + + @Test + void isFatalEnabledWithMarker() { + assertTrue(logger.isFatalEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.FATAL, marker), "Incorrect level"); + } + + @Test + void isInfoEnabledWithMarker() { + assertTrue(logger.isInfoEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.INFO, marker), "Incorrect level"); + } + + @Test + void isOffEnabledWithMarker() { + assertTrue(logger.isEnabled(Level.OFF, marker), "Incorrect level"); + } + + @Test + void isTraceEnabledWithMarker() { + assertTrue(logger.isTraceEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.TRACE, marker), "Incorrect level"); + } + + @Test + void isWarnEnabledWithMarker() { + assertTrue(logger.isWarnEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.WARN, marker), "Incorrect level"); + } + + @Test + @UsingThreadContextMap + void mdc() { + ThreadContext.put("TestYear", Integer.toString(2010)); + logger.debug("Debug message"); + final 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 + 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 + void setup() { + results.clear(); + } + + @Test + 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 + 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..411dd5766cd --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/MarkerTest.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; + +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.Log4jStaticResources; +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 = Log4jStaticResources.MARKER_MANAGER, mode = ResourceAccessMode.READ_WRITE) +class MarkerTest { + + @BeforeEach + void setUp() { + MarkerManager.clear(); + } + + @Test + void testGetMarker() { + final Marker expected = MarkerManager.getMarker("A"); + assertNull(expected.getParents()); + } + + @Test + 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 + void testHasParents() { + final Marker parent = MarkerManager.getMarker("PARENT"); + final Marker existing = MarkerManager.getMarker("EXISTING"); + assertFalse(existing.hasParents()); + existing.setParents(parent); + assertTrue(existing.hasParents()); + } + + @Test + void testMarker() { + // root (level 1) + final Marker parent = MarkerManager.getMarker("PARENT"); + // level 2 + 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"); + assertFalse(parent.isInstanceOf(test1)); + assertFalse(parent.isInstanceOf(test2)); + // level 3 + final Marker test3 = MarkerManager.getMarker("TEST3").addParents(test2); + assertTrue(test3.isInstanceOf(test2)); + assertTrue(test3.isInstanceOf("TEST2")); + assertTrue(test3.isInstanceOf("PARENT")); + assertTrue(test2.isInstanceOf("PARENT")); + assertFalse(parent.isInstanceOf(test3)); + assertFalse(parent.isInstanceOf(test3)); + } + + @Test + void testMarkerSharedIntermediaryMarker() { + final Marker parent1 = MarkerManager.getMarker("PARENT1"); + final Marker parent2 = MarkerManager.getMarker("PARENT2"); + final Marker test1 = MarkerManager.getMarker("TEST1").setParents(parent1, parent2); + assertTrue(test1.isInstanceOf(parent1)); + // Leaf + final Marker leaf = MarkerManager.getMarker("LEAF").setParents(test1); + assertTrue(leaf.isInstanceOf("TEST1")); + assertTrue(leaf.isInstanceOf("PARENT1")); + assertTrue(leaf.isInstanceOf("PARENT2")); + } + + @Test + 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("PARENT1"), "TEST1 is not an instance of PARENT1"); + assertTrue(test1.isInstanceOf(parent2), "TEST1 is not an instance of PARENT2"); + 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("PARENT1"), "TEST2 is not an instance of PARENT1"); + assertTrue(test2.isInstanceOf(parent2), "TEST2 is not an instance of PARENT2"); + assertTrue(test2.isInstanceOf("PARENT2"), "TEST2 is not an instance of PARENT2"); + } + + @Test + 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("PARENT"), "TEST1 is not an instance of PARENT"); + assertTrue(test1.isInstanceOf(existing), "TEST1 is not an instance of EXISTING"); + assertTrue(test1.isInstanceOf("EXISTING"), "TEST1 is not an instance of EXISTING"); + } + + @Test + 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("PARENT"), "TEST1 is not an instance of PARENT"); + assertTrue(test1.isInstanceOf(existing), "TEST1 is not an instance of EXISTING"); + 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..c4d9bebd81c --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.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; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ThreadContext}. + */ +@SetTestProperty(key = "log4j2.disableThreadContext", value = "true") +@SetTestProperty(key = "log4j2.disableThreadContextMap", value = "true") +@UsingThreadContextMap +class NoopThreadContextTest { + + @Test + void testNoop() { + ThreadContext.put("Test", "Test"); + final String value = ThreadContext.get("Test"); + assertNull(value, "value was saved"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java new file mode 100644 index 00000000000..1aa91ae529d --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.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; + +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(5, CURRENT_VERSION, 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..8e398910d14 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.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.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.spi.DefaultThreadContextMap; +import org.apache.logging.log4j.test.ThreadContextUtilityClass; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ThreadContext}. + */ +@SetTestProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true") +@UsingThreadContextMap +@UsingThreadContextStack +class ThreadContextInheritanceTest { + + @BeforeAll + static void setupClass() { + System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true"); + ThreadContext.init(); + } + + @AfterAll + static void tearDownClass() { + System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); + ThreadContext.init(); + } + + @Test + void testPush() { + ThreadContext.push("Hello"); + ThreadContext.push("{} is {}", ThreadContextInheritanceTest.class.getSimpleName(), "running"); + assertEquals( + "ThreadContextInheritanceTest is running", ThreadContext.pop(), "Incorrect parameterized stack value"); + assertEquals("Hello", ThreadContext.pop(), "Incorrect simple stack value"); + } + + @Test + @Tag("performance") + void perfTest() { + ThreadContextUtilityClass.perfTest(); + } + + @Test + void testGetContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetContextReturnsEmptyMapIfEmpty(); + } + + @Test + void testGetContextReturnsMutableCopy() { + ThreadContextUtilityClass.testGetContextReturnsMutableCopy(); + } + + @Test + void testGetImmutableContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsEmptyMapIfEmpty(); + } + + @Test + void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfNonEmpty(); + } + + @Test + void testGetImmutableContextReturnsImmutableMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfEmpty(); + } + + @Test + void testGetImmutableStackReturnsEmptyStackIfEmpty() { + ThreadContextUtilityClass.testGetImmutableStackReturnsEmptyStackIfEmpty(); + } + + @Test + void testPut() { + ThreadContextUtilityClass.testPut(); + } + + @Test + 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 + void testContainsKey() { + ThreadContext.clearMap(); + 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-test/src/test/java/org/apache/logging/log4j/ThreadContextTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextTest.java new file mode 100644 index 00000000000..a06fd7c2611 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextTest.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.test.ThreadContextUtilityClass; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@UsingAnyThreadContext +class ThreadContextTest { + + @Test + void testPush() { + ThreadContext.push("Hello"); + ThreadContext.push("{} is {}", ThreadContextTest.class.getSimpleName(), "running"); + assertEquals("ThreadContextTest is running", ThreadContext.pop(), "Incorrect parameterized stack value"); + assertEquals("Hello", ThreadContext.pop(), "Incorrect simple stack value"); + } + + @Test + void testInheritanceSwitchedOffByDefault() throws Exception { + ThreadContext.put("Greeting", "Hello"); + StringBuilder sb = new StringBuilder(); + TestThread thread = new TestThread(sb); + thread.start(); + thread.join(); + String str = sb.toString(); + assertEquals("null", str, "Unexpected ThreadContext value. Expected null. Actual " + str); + sb = new StringBuilder(); + thread = new TestThread(sb); + thread.start(); + thread.join(); + str = sb.toString(); + assertEquals("null", str, "Unexpected ThreadContext value. Expected null. Actual " + str); + } + + @Test + @Tag("performance") + void perfTest() { + ThreadContextUtilityClass.perfTest(); + } + + @Test + void testGetContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetContextReturnsEmptyMapIfEmpty(); + } + + @Test + void testGetContextReturnsMutableCopy() { + ThreadContextUtilityClass.testGetContextReturnsMutableCopy(); + } + + @Test + void testGetImmutableContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsEmptyMapIfEmpty(); + } + + @Test + void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfNonEmpty(); + } + + @Test + void testGetImmutableContextReturnsImmutableMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfEmpty(); + } + + @Test + void testGetImmutableStackReturnsEmptyStackIfEmpty() { + ThreadContextUtilityClass.testGetImmutableStackReturnsEmptyStackIfEmpty(); + } + + @Test + void testPut() { + ThreadContextUtilityClass.testPut(); + } + + @Test + 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 + void testPutAll() { + assertTrue(ThreadContext.isEmpty()); + assertFalse(ThreadContext.containsKey("key")); + final int mapSize = 10; + final Map newMap = new HashMap<>(mapSize); + for (int i = 1; i <= mapSize; i++) { + newMap.put("key" + i, "value" + i); + } + ThreadContext.putAll(newMap); + assertFalse(ThreadContext.isEmpty()); + for (int i = 1; i <= mapSize; i++) { + assertTrue(ThreadContext.containsKey("key" + i)); + assertEquals("value" + i, ThreadContext.get("key" + i)); + } + } + + @Test + 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 + void testRemoveAll() { + ThreadContext.put("testKey1", "testValue1"); + ThreadContext.put("testKey2", "testValue2"); + assertEquals("testValue1", ThreadContext.get("testKey1")); + assertEquals("testValue2", ThreadContext.get("testKey2")); + assertFalse(ThreadContext.isEmpty()); + + ThreadContext.removeAll(Arrays.asList("testKey1", "testKey2")); + assertNull(ThreadContext.get("testKey1")); + assertNull(ThreadContext.get("testKey2")); + assertTrue(ThreadContext.isEmpty()); + } + + @Test + 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-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java new file mode 100644 index 00000000000..dcc16803baa --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java @@ -0,0 +1,375 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +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.ObjectMessage; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.ReusableParameterizedMessage; +import org.apache.logging.log4j.message.ReusableParameterizedMessageTest; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.junit.jupiter.api.Test; + +class TraceLoggingTest extends AbstractLogger { + static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq"); + private int charSeqCount; + private int objectCount; + + 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 final long serialVersionUID = 1L; + + private static Level currentLevel; + + private LogEvent currentEvent; + + 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 static final String marker = "TEST"; + + @Override + public Level getLevel() { + return currentLevel; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { + return true; + // 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) { + 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); + } + } + + @Test + void testTraceEntryExit() { + currentLevel = Level.TRACE; + final FlowMessageFactory fact = new DefaultFlowMessageFactory(); + + final ParameterizedMessage paramMsg = new ParameterizedMessage("Tracy {}", "Logan"); + currentEvent = new LogEvent(ENTRY_MARKER.getName(), fact.newEntryMessage(paramMsg), null); + final EntryMessage entry = traceEntry("Tracy {}", "Logan"); + + final ReusableParameterizedMessage msg = + ReusableParameterizedMessageTest.set(new ReusableParameterizedMessage(), "Tracy {}", "Logan"); + ReusableParameterizedMessageTest.set(msg, "Some other message {}", 123); + currentEvent = new LogEvent(null, msg, null); + trace("Some other message {}", 123); + + // ensure original entry message not overwritten + assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); + + currentEvent = new LogEvent(EXIT_MARKER.getName(), fact.newExitMessage(entry), null); + traceExit(entry); + + // ensure original entry message not overwritten + assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); + } + + @Test + void testTraceEntryMessage() { + currentLevel = Level.TRACE; + final FlowMessageFactory fact = new DefaultFlowMessageFactory(); + + final ParameterizedMessage paramMsg = new ParameterizedMessage("Tracy {}", "Logan"); + currentEvent = new LogEvent(ENTRY_MARKER.getName(), fact.newEntryMessage(paramMsg), null); + + final ReusableParameterizedMessage msg = + ReusableParameterizedMessageTest.set(new ReusableParameterizedMessage(), "Tracy {}", "Logan"); + final EntryMessage entry = traceEntry(msg); + + ReusableParameterizedMessageTest.set(msg, "Some other message {}", 123); + currentEvent = new LogEvent(null, msg, null); + trace("Some other message {}", 123); + + // ensure original entry message not overwritten + assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); + + currentEvent = new LogEvent(EXIT_MARKER.getName(), fact.newExitMessage(entry), null); + traceExit(entry); + + // ensure original entry message not overwritten + assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/DefaultLogBuilderTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/DefaultLogBuilderTest.java new file mode 100644 index 00000000000..4bff93565cd --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/DefaultLogBuilderTest.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.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.test.TestLogger; +import org.junit.jupiter.api.Test; + +class DefaultLogBuilderTest { + + private final TestLogger logger1 = (TestLogger) LogManager.getLogger(DefaultLogBuilderTest.class); + private final TestLogger logger2 = (TestLogger) LogManager.getLogger("second.logger"); + + @Test + void testConcurrentUsage() { + logger1.getEntries().clear(); + logger2.getEntries().clear(); + final List logBuilders = + Arrays.asList(logger1.atDebug(), logger1.atInfo(), logger2.atDebug(), logger2.atInfo()); + logBuilders.forEach(logBuilder -> logBuilder.log("Hello LogBuilder!")); + assertThat(logger1.getEntries()) + .hasSize(2) + .containsExactly(" DEBUG Hello LogBuilder!", " INFO Hello LogBuilder!"); + assertThat(logger2.getEntries()) + .hasSize(2) + .containsExactly(" DEBUG Hello LogBuilder!", " INFO Hello LogBuilder!"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/map/UnmodifiableArrayBackedMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/map/UnmodifiableArrayBackedMapTest.java new file mode 100644 index 00000000000..612840bbf5e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/map/UnmodifiableArrayBackedMapTest.java @@ -0,0 +1,397 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +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 static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.TriConsumer; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class UnmodifiableArrayBackedMapTest { + private static final int TEST_DATA_SIZE = 5; + + private HashMap getTestParameters() { + return getTestParameters(TEST_DATA_SIZE); + } + + private HashMap getTestParameters(int numParams) { + HashMap params = new LinkedHashMap<>(); + for (int i = 0; i < numParams; i++) { + params.put("" + i, "value" + i); + } + + return params; + } + + @Test + void testCopyAndPut() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + testMap = testMap.copyAndPut("6", "value6"); + assertTrue(testMap.containsKey("6")); + assertEquals("value6", testMap.get("6")); + + testMap = testMap.copyAndPut("6", "another value"); + assertTrue(testMap.containsKey("6")); + assertEquals("another value", testMap.get("6")); + + HashMap newValues = getTestParameters(); + testMap = testMap.copyAndPutAll(newValues); + assertEquals("value1", testMap.get("1")); + assertEquals("value4", testMap.get("4")); + assertEquals("another value", testMap.get("6")); + } + + @Test + void testCopyAndRemove() { + // general removing from well-populated set + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + testMap = testMap.copyAndRemove("2"); + testMap = testMap.copyAndRemove("not_present"); + assertEquals(4, testMap.size()); + assertFalse(testMap.containsKey("2")); + assertTrue(testMap.containsKey("1")); + assertFalse(testMap.containsValue("value2")); + + // test removing from empty set + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPut("test", "test"); + testMap = testMap.copyAndRemove("test"); + assertTrue(testMap.isEmpty()); + + // test removing first of two elements + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPut("test1", "test1"); + testMap = testMap.copyAndPut("test2", "test2"); + testMap = testMap.copyAndRemove("test1"); + assertFalse(testMap.containsKey("test1")); + assertTrue(testMap.containsKey("test2")); + + // test removing second of two elements + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPut("test1", "test1"); + testMap = testMap.copyAndPut("test2", "test2"); + testMap = testMap.copyAndRemove("test2"); + assertTrue(testMap.containsKey("test1")); + assertFalse(testMap.containsKey("test2")); + } + + @Test + void testCopyAndRemoveAll() { + HashMap initialMapContents = getTestParameters(15); + initialMapContents.put("extra_key", "extra_value"); + + HashSet keysToRemove = new LinkedHashSet<>(); + keysToRemove.add("3"); + keysToRemove.add("11"); + keysToRemove.add("definitely_not_found"); + + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(initialMapContents); + testMap = testMap.copyAndRemoveAll(keysToRemove); + assertEquals(14, testMap.size()); + + assertFalse(testMap.containsKey("3")); + assertFalse(testMap.containsValue("value3")); + assertFalse(testMap.containsKey("11")); + assertFalse(testMap.containsValue("value11")); + + assertTrue(testMap.containsKey("extra_key")); + assertTrue(testMap.containsValue("extra_value")); + assertTrue(testMap.containsKey("1")); + assertTrue(testMap.containsValue("value1")); + assertTrue(testMap.containsKey("0")); + assertTrue(testMap.containsValue("value0")); + assertTrue(testMap.containsKey("14")); + assertTrue(testMap.containsValue("value14")); + + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(initialMapContents); + UnmodifiableArrayBackedMap testMapWithArrayListRemoval = + testMap.copyAndRemoveAll(new ArrayList<>(keysToRemove)); + UnmodifiableArrayBackedMap testMapWithSetRemoval = testMap.copyAndRemoveAll(keysToRemove); + assertEquals(testMapWithSetRemoval, testMapWithArrayListRemoval); + + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + assertEquals(0, testMap.copyAndRemoveAll(initialMapContents.keySet()).size()); + + testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPut("test", "test"); + assertEquals(1, testMap.copyAndRemoveAll(initialMapContents.keySet()).size()); + testMap = testMap.copyAndRemoveAll(Collections.singleton("not found")); + assertEquals(0, testMap.copyAndRemoveAll(testMap.keySet()).size()); + testMap = testMap.copyAndRemoveAll(Collections.singleton("test")); + assertEquals(0, testMap.copyAndRemoveAll(testMap.keySet()).size()); + } + + @Test + void testEmptyMap() { + assertNull(UnmodifiableArrayBackedMap.EMPTY_MAP.get("test")); + } + + @Test + void testEntrySetIteratorAndSize() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + Set> entrySet = testMap.entrySet(); + int numEntriesFound = 0; + for (@SuppressWarnings("unused") Map.Entry entry : entrySet) { + numEntriesFound++; + } + + assertEquals(testMap.size(), numEntriesFound); + assertEquals(testMap.size(), entrySet.size()); + } + + @Test + void testEntrySetMutatorsBlocked() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + Set> entrySet = testMap.entrySet(); + for (Map.Entry entry : entrySet) { + try { + entry.setValue("test"); + fail("Entry.setValue() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + for (@SuppressWarnings("unused") Map.Entry entry : entrySet) { + try { + entrySet.add(null); + fail("EntrySet.add() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + for (@SuppressWarnings("unused") Map.Entry entry : entrySet) { + try { + entrySet.addAll(new HashSet<>()); + fail("EntrySet.addAll() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + } + + /** + * Tests various situations with .equals(). Test tries comparisons in both + * directions, to make sure that HashMap.equals(UnmodifiableArrayBackedMap) work + * as well as UnmodifiableArrayBackedMap.equals(HashMap). + */ + @Test + void testEqualsHashCodeWithIdenticalContent() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + assertEquals(params, testMap); + assertEquals(testMap, params); + assertEquals(params.hashCode(), testMap.hashCode()); + } + + @Test + void testEqualsHashCodeWithOneEmptyMap() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + // verify empty maps are not equal to non-empty maps + assertNotEquals(UnmodifiableArrayBackedMap.EMPTY_MAP, params); + assertNotEquals(new HashMap<>(), testMap); + assertNotEquals(UnmodifiableArrayBackedMap.EMPTY_MAP, params); + assertNotEquals(new HashMap<>(), testMap); + } + + @Test + void testEqualsHashCodeWithOneKeyRemoved() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + + params.remove("1"); + assertNotEquals(params, testMap); + assertNotEquals(testMap, params); + + testMap = testMap.copyAndRemove("1").copyAndRemove("2"); + assertNotEquals(params, testMap); + assertNotEquals(testMap, params); + } + + @Test + void testEqualsWhenOneValueDiffers() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + assertNotEquals(params, testMap.copyAndPut("1", "different value")); + assertNotEquals(testMap.copyAndPut("1", "different value"), params); + } + + @Test + void testForEachBiConsumer_JavaUtil() { + final Map map = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + final Collection keys = new HashSet<>(); + map.forEach((key, value) -> keys.add(key)); + assertEquals(map.keySet(), keys); + } + + @Test + void testForEachBiConsumer_Log4jUtil() { + final ReadOnlyStringMap map = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + final Collection keys = new HashSet<>(); + map.forEach((key, value) -> keys.add(key)); + assertEquals(map.toMap().keySet(), keys); + } + + @Test + void testForEachTriConsumer() { + UnmodifiableArrayBackedMap map = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + HashMap iterationResultMap = new HashMap<>(); + TriConsumer> triConsumer = + new TriConsumer>() { + @Override + public void accept(String k, String v, Map s) { + s.put(k, v); + } + }; + map.forEach(triConsumer, iterationResultMap); + assertEquals(map, iterationResultMap); + } + + @Test + void testImmutability() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap originalMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + UnmodifiableArrayBackedMap modifiedMap = originalMap.copyAndPutAll(getTestParameters()); + assertEquals(originalMap, params); + + modifiedMap = modifiedMap.copyAndRemoveAll(modifiedMap.keySet()); + assertTrue(modifiedMap.isEmpty()); + + assertEquals(originalMap, params); + } + + @Test + void testInstanceCopy() { + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + + UnmodifiableArrayBackedMap testMap2 = new UnmodifiableArrayBackedMap(testMap); + assertEquals(testMap, testMap2); + } + + @Test + void testMutatorsBlocked() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + try { + testMap.put("a", "a"); + fail("put() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.putAll(new HashMap<>()); + fail("putAll() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.remove("1"); + fail("remove() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + + try { + testMap.clear(); + fail("clear() wasn't blocked"); + } catch (UnsupportedOperationException e) { + } + } + + @Test + void testNullValue() { + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + testMap = testMap.copyAndPut("key", null); + assertTrue(testMap.containsKey("key")); + assertTrue(testMap.containsValue(null)); + assertEquals(1, testMap.size()); + assertNull(testMap.get("key")); + } + + @Test + void testReads() { + assertNull(UnmodifiableArrayBackedMap.EMPTY_MAP.get("test")); + HashMap params = getTestParameters(); + UnmodifiableArrayBackedMap testMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(params); + for (Map.Entry entry : params.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + assertTrue(testMap.containsKey(key)); + assertTrue(testMap.containsValue(value)); + assertEquals(testMap.get(key), params.get(key)); + } + assertFalse(testMap.containsKey("not_present")); + assertFalse(testMap.containsValue("not_present")); + assertNull(testMap.get("not_present")); + } + + @Test + void testState() { + UnmodifiableArrayBackedMap originalMap; + UnmodifiableArrayBackedMap newMap; + + originalMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + newMap = UnmodifiableArrayBackedMap.getMap(originalMap.getBackingArray()); + assertEquals(originalMap, newMap); + + originalMap = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPutAll(getTestParameters()); + newMap = UnmodifiableArrayBackedMap.getMap(originalMap.getBackingArray()); + assertEquals(originalMap, newMap); + + originalMap = UnmodifiableArrayBackedMap.EMPTY_MAP + .copyAndPutAll(getTestParameters()) + .copyAndRemove("1"); + newMap = UnmodifiableArrayBackedMap.getMap(originalMap.getBackingArray()); + assertEquals(originalMap, newMap); + } + + @Test + void testToMap() { + UnmodifiableArrayBackedMap map = UnmodifiableArrayBackedMap.EMPTY_MAP.copyAndPut("test", "test"); + // verify same instance, not just equals() + assertSame(map, map.toMap()); + } + + @Test + void copyAndRemoveAll_should_work() { + + // Create the actual map + UnmodifiableArrayBackedMap actualMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + actualMap = actualMap.copyAndPut("outer", "two"); + actualMap = actualMap.copyAndPut("inner", "one"); + actualMap = actualMap.copyAndPut("not-in-closeable", "true"); + + // Create the expected map + UnmodifiableArrayBackedMap expectedMap = UnmodifiableArrayBackedMap.EMPTY_MAP; + expectedMap = expectedMap.copyAndPut("outer", "two"); + expectedMap = expectedMap.copyAndPut("not-in-closeable", "true"); + + // Remove the key and verify + actualMap = actualMap.copyAndRemoveAll(Collections.singleton("inner")); + Assertions.assertThat(actualMap).isEqualTo(expectedMap); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java new file mode 100644 index 00000000000..f2a35b2474a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.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.message; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.apache.logging.log4j.util.Constants; +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; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +class FormattedMessageTest { + + private static final String SPACE = Constants.JAVA_MAJOR_VERSION < 9 ? " " : "\u00a0"; + + private static final int LOOP_CNT = 500; + String[] array = new String[LOOP_CNT]; + + @Test + void testStringNoArgs() { + final String testMsg = "Test message %1s"; + FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + final String expected = "Test message null"; + assertEquals(expected, result); + final Object[] array = null; + msg = new FormattedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertEquals(expected, result); + } + + @Test + void tesStringOneStringArg() { + final String testMsg = "Test message %1s"; + final FormattedMessage msg = new FormattedMessage(testMsg, "Apache"); + final String result = msg.getFormattedMessage(); + final String expected = "Test message Apache"; + assertEquals(expected, result); + } + + @Test + void tesStringOneArgLocaleFrance_StringFormattedMessage() { + final String testMsg = "Test message e = %+10.4f"; + final FormattedMessage msg = new FormattedMessage(Locale.FRANCE, testMsg, Math.E); + final String result = msg.getFormattedMessage(); + final String expected = "Test message e = +2,7183"; + assertEquals(expected, result); + } + + @Test + void tesStringOneArgLocaleFrance_MessageFormatMessage() { + final String testMsg = "Test message {0,number,currency}"; + final FormattedMessage msg = new FormattedMessage(Locale.FRANCE, testMsg, 12); + final String result = msg.getFormattedMessage(); + final String expected = "Test message 12,00" + SPACE + "€"; + assertEquals(expected, result); + } + + @Test + void tesStringOneArgLocaleUs_MessageFormatMessage() { + final String testMsg = "Test message {0,number,currency}"; + final FormattedMessage msg = new FormattedMessage(Locale.US, testMsg, 12); + final String result = msg.getFormattedMessage(); + final String expected = "Test message $12.00"; + assertEquals(expected, result); + } + + @Test + void tesStringOneArgLocaleUs() { + final String testMsg = "Test message e = %+10.4f"; + final FormattedMessage msg = new FormattedMessage(Locale.US, testMsg, Math.E); + final String result = msg.getFormattedMessage(); + final String expected = "Test message e = +2.7183"; + assertEquals(expected, result); + } + + @Test + void testNoArgs() { + final String testMsg = "Test message {0}"; + FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + final String expected = "Test message {0}"; + assertEquals(expected, result); + final Object[] array = null; + msg = new FormattedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertEquals(expected, result); + } + + @Test + void testOneArg() { + final String testMsg = "Test message {0}"; + final FormattedMessage msg = new FormattedMessage(testMsg, "Apache"); + final String result = msg.getFormattedMessage(); + final String expected = "Test message Apache"; + assertEquals(expected, result); + } + + @Test + void testParamNoArgs() { + final String testMsg = "Test message {}"; + FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + assertEquals(testMsg, result); + final Object[] array = null; + msg = new FormattedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertEquals(testMsg, result); + } + + @Test + void testUnsafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final FormattedMessage msg = new FormattedMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); + } + + @Test + void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final FormattedMessage msg = new FormattedMessage(testMsg, param); + + // modify parameter after calling msg.getFormattedMessage + msg.getFormattedMessage(); // freeze the formatted message + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message abc", actual, "Should use initial param value"); + } + + @Test + void testSerialization() throws IOException, ClassNotFoundException { + final FormattedMessage expected = new FormattedMessage("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 FormattedMessage actual = (FormattedMessage) in.readObject(); + 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 85% 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 index 5107841f380..b415a17f615 100644 --- 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 @@ -1,25 +1,24 @@ /* * 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.logging.log4j.message; -import org.apache.logging.log4j.status.StatusLogger; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.status.StatusLogger; /** * Converts an Object to a JSON String. 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..06d3fb8088e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.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.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Locale; +import java.util.ResourceBundle; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link LocalizedMessageFactory}. + */ +class LocalizedMessageFactoryTest { + + @Test + 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 + 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 + 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 + 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 + 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()); + } + + @Test + 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()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java new file mode 100644 index 00000000000..832230d53c8 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.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.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.Serializable; +import java.util.Locale; +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; + +/** + * Tests LocalizedMessage. + */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +class LocalizedMessageTest { + + private T roundtrip(final T msg) { + return SerializationUtils.roundtrip(msg); + } + + @Test + void testMessageFormat() { + final LocalizedMessage msg = + new LocalizedMessage("MF", new Locale("en", "US"), "msg1", new Object[] {"1", "Test"}); + assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); + } + + @Test + void testSerializationMessageFormat() { + final LocalizedMessage msg = + new LocalizedMessage("MF", new Locale("en", "US"), "msg1", new Object[] {"1", "Test"}); + assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); + final LocalizedMessage msg2 = roundtrip(msg); + assertEquals("This is test number 1 with string argument Test.", msg2.getFormattedMessage()); + } + + @Test + void testSerializationStringFormat() { + final LocalizedMessage msg = + new LocalizedMessage("SF", new Locale("en", "US"), "msg1", new Object[] {"1", "Test"}); + assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); + final LocalizedMessage msg2 = roundtrip(msg); + assertEquals("This is test number 1 with string argument Test.", msg2.getFormattedMessage()); + } + + @Test + void testStringFormat() { + final LocalizedMessage msg = + new LocalizedMessage("SF", new Locale("en", "US"), "msg1", new Object[] {"1", "Test"}); + assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); + } + + @Test + void testUnsafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final LocalizedMessage msg = new LocalizedMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); + } + + @Test + void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final LocalizedMessage msg = new LocalizedMessage(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"); + } +} 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..a36bb6b4ae9 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java @@ -0,0 +1,330 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigDecimal; +import java.sql.Date; +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 java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.junit.jupiter.api.Test; + +/** + * + */ +class MapMessageTest { + + @Test + 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 + 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 + 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 + 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 + 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 + 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 + 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 + void testJsonFormatterNestedObjectSupport() { + final Map map = new LinkedHashMap<>(); + map.put("chars", new char[] {'a', 'b', 'c'}); + map.put("booleans", new boolean[] {true, false}); + map.put("bytes", new byte[] {1, 2}); + map.put("shorts", new short[] {3, 4}); + map.put("ints", new int[] {256, 257}); + map.put("longs", new long[] {2147483648L, 2147483649L}); + map.put("floats", new float[] {1.0F, 1.1F}); + map.put("doubles", new double[] {2.0D, 2.1D}); + map.put("objects", new Object[] {"foo", "bar"}); + 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", map) + .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 + 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 + void testJsonFormatterMaxDepthViolation() { + assertThrows( + IllegalArgumentException.class, () -> testJsonFormatterMaxDepth(MapMessageJsonFormatter.MAX_DEPTH - 1)); + } + + @Test + void testJsonFormatterMaxDepthConformance() { + final int depth = MapMessageJsonFormatter.MAX_DEPTH - 2; + final String expectedJson = String.format( + "{'key':%s1%s}", StringUtils.repeat("[", depth), StringUtils.repeat("]", depth)) + .replace('\'', '"'); + final String actualJson = testJsonFormatterMaxDepth(depth); + assertEquals(expectedJson, actualJson); + } + + public static String testJsonFormatterMaxDepth(final int depth) { + List list = new LinkedList<>(); + list.add(1); + int currentDepth = depth; + while (--currentDepth > 0) { + list = new LinkedList<>(Collections.singletonList(list)); + } + return new ObjectMapMessage().with("key", list).getFormattedMessage(new String[] {"JSON"}); + } + + @Test + 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 + 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 + void testGetNonStringValue() { + final String key = "Key"; + final ObjectMapMessage msg = new ObjectMapMessage().with(key, 1L); + assertEquals("1", msg.get(key)); + } + + @Test + void testRemoveNonStringValue() { + final String key = "Key"; + final ObjectMapMessage msg = new ObjectMapMessage().with(key, 1L); + assertEquals("1", msg.remove(key)); + } + + @Test + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + void testTime() { + final Time time = new Time(12, 5, 5); + final ObjectMapMessage message = new ObjectMapMessage().with("time", time); + assertEquals("time=\"" + time + "\"", message.getFormattedMessage(), "Incorrect time format"); + } + + @Test + void testDate() { + final Date date = new Date(System.currentTimeMillis()); + final ObjectMapMessage message = new ObjectMapMessage().with("date", date); + assertEquals("date=\"" + date + "\"", message.getFormattedMessage(), "Incorrect date format"); + } + + private static final class FormattableTestType implements StringBuilderFormattable { + + @Override + public String toString() { + return "toString"; + } + + @Override + public void formatTo(final 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..86823f14788 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.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; + +import java.util.Arrays; +import java.util.Collection; +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; +import org.junit.runners.Parameterized; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +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-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java new file mode 100644 index 00000000000..44fc67cc383 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.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.message; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Locale; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.util.Constants; +import org.assertj.core.presentation.UnicodeRepresentation; +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; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +class MessageFormatMessageTest { + + private static final char SPACE = ' '; + private static final char NB_SPACE = '\u00a0'; + private static final char NARROW_NB_SPACE = '\u202f'; + + private static final int LOOP_CNT = 500; + String[] array = new String[LOOP_CNT]; + + @Test + void testNoArgs() { + final String testMsg = "Test message {0}"; + MessageFormatMessage msg = new MessageFormatMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + String expected = "Test message {0}"; + assertEquals(expected, result); + final Object[] array = null; + msg = new MessageFormatMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + expected = "Test message null"; + assertEquals(expected, result); + } + + @Test + void testOneStringArg() { + final String testMsg = "Test message {0}"; + final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache"); + final String result = msg.getFormattedMessage(); + final String expected = "Test message Apache"; + assertEquals(expected, result); + } + + @Test + void testOneIntArgLocaleUs() { + final String testMsg = "Test message {0,number,currency}"; + final MessageFormatMessage msg = new MessageFormatMessage(Locale.US, testMsg, 1234567890); + final String result = msg.getFormattedMessage(); + final String expected = "Test message $1,234,567,890.00"; + assertEquals(expected, result); + } + + @Test + 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 char separator = Constants.JAVA_MAJOR_VERSION < 9 ? SPACE : NB_SPACE; + final char groupingSeparator = Constants.JAVA_MAJOR_VERSION < 17 ? NB_SPACE : NARROW_NB_SPACE; + assertThat(result) + .withRepresentation(UnicodeRepresentation.UNICODE_REPRESENTATION) + .isEqualTo("Test message 1%1$c234%1$c567%1$c890,00%2$c€", groupingSeparator, separator); + } + + @Test + 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 + void testUnsafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message {0}"; + final Mutable param = new Mutable().set("abc"); + final MessageFormatMessage msg = new MessageFormatMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); + } + + @Test + void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 + final String testMsg = "Test message {0}"; + final Mutable param = new Mutable().set("abc"); + final MessageFormatMessage msg = new MessageFormatMessage(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"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java new file mode 100644 index 00000000000..90fe41b7aed --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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) +class MessageFormatsPerfTest { + + private static final int LOOP_CNT = 500; + String[] array = new String[LOOP_CNT]; + private static long stringTime = 0; + private static long paramTime = 0; + private static long msgFormatTime = 0; + private static long formattedTime = 0; + + @AfterAll + static void after() { + if (stringTime > paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times faster than StringFormat.", ((float) stringTime / paramTime))); + } else if (stringTime < paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times slower than StringFormat.", ((float) paramTime / stringTime))); + } else { + System.out.println("The speed of Parameterized and StringFormat are the same"); + } + if (msgFormatTime > paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times faster than MessageFormat.", ((float) msgFormatTime / paramTime))); + } else if (msgFormatTime < paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times slower than MessageFormat.", ((float) paramTime / msgFormatTime))); + } else { + System.out.println("The speed of Parameterized and MessageFormat are the same"); + } + if (formattedTime > paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times faster than Formatted.", ((float) formattedTime / paramTime))); + } else if (formattedTime < paramTime) { + System.out.println(String.format( + "Parameterized is %1$.2f times slower than Formatted.", ((float) paramTime / formattedTime))); + } else { + System.out.println("The speed of Parameterized and Formatted are the same"); + } + } + + @Test + void testStringPerf() { + final String testMsg = "Test message %1s %2s"; + final Timer timer = new Timer("StringFormat", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + final StringFormattedMessage msg = new StringFormattedMessage(testMsg, "Apache", "Log4j"); + array[i] = msg.getFormattedMessage(); + } + timer.stop(); + stringTime = timer.getElapsedNanoTime(); + System.out.println(timer); + } + + @Test + void testMessageFormatPerf() { + final String testMsg = "Test message {0} {1}"; + final Timer timer = new Timer("MessageFormat", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache", "Log4j"); + array[i] = msg.getFormattedMessage(); + } + timer.stop(); + msgFormatTime = timer.getElapsedNanoTime(); + System.out.println(timer); + } + + @Test + void testParameterizedPerf() { + final String testMsg = "Test message {} {}"; + final Timer timer = new Timer("Parameterized", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + final ParameterizedMessage msg = new ParameterizedMessage(testMsg, "Apache", "Log4j"); + array[i] = msg.getFormattedMessage(); + } + timer.stop(); + paramTime = timer.getElapsedNanoTime(); + System.out.println(timer); + } + + @Test + void testFormattedParameterizedPerf() { + final String testMsg = "Test message {} {}"; + final Timer timer = new Timer("FormattedParameterized", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + final FormattedMessage msg = new FormattedMessage(testMsg, "Apache", "Log4j"); + array[i] = msg.getFormattedMessage(); + } + timer.stop(); + formattedTime = timer.getElapsedNanoTime(); + System.out.println(timer); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java new file mode 100644 index 00000000000..cdfb2c9bd26 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.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.message; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** + * @since 2.4 + */ +class ObjectArrayMessageTest { + + private static final Object[] ARRAY = {"A", "B", "C"}; + private static final ObjectArrayMessage OBJECT_ARRAY_MESSAGE = new ObjectArrayMessage(ARRAY); + + @Test + void testGetParameters() { + assertArrayEquals(ARRAY, OBJECT_ARRAY_MESSAGE.getParameters()); + } + + @Test + void testGetThrowable() { + assertNull(OBJECT_ARRAY_MESSAGE.getThrowable()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java new file mode 100644 index 00000000000..70c861208a1 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.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.message; + +class ObjectMapMessage extends MapMessage { + + private static final long serialVersionUID = 1L; +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java new file mode 100644 index 00000000000..2bdc1d7a68a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.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.logging.log4j.message; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link ObjectMessage}. + */ +class ObjectMessageTest { + + @Test + void testNull() { + final ObjectMessage msg = new ObjectMessage(null); + final String result = msg.getFormattedMessage(); + assertEquals("null", result); + } + + @Test + void testNotNull() { + final String testMsg = "Test message {}"; + final ObjectMessage msg = new ObjectMessage(testMsg); + final String result = msg.getFormattedMessage(); + assertEquals(testMsg, result); + } + + @Test + void testUnsafeWithMutableParams() { // LOG4J2-763 + final Mutable param = new Mutable().set("abc"); + final ObjectMessage msg = new ObjectMessage(param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("XYZ", actual, "Expected most recent param value"); + } + + @Test + void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 + final Mutable param = new Mutable().set("abc"); + final ObjectMessage msg = new ObjectMessage(param); + + // modify parameter after calling msg.getFormattedMessage + msg.getFormattedMessage(); + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("abc", actual, "Should use initial param value"); + } + + @Test + void formatTo_usesCachedMessageString() { + final StringBuilder charSequence = new StringBuilder("initial value"); + final ObjectMessage message = new ObjectMessage(charSequence); + assertEquals("initial value", message.getFormattedMessage()); + + charSequence.setLength(0); + charSequence.append("different value"); + + final StringBuilder result = new StringBuilder(); + message.formatTo(result); + assertEquals("initial value", result.toString()); + } + + static Stream testSerializable() { + @SuppressWarnings("EqualsHashCode") + class NonSerializable { + @Override + public boolean equals(final Object other) { + return other instanceof NonSerializable; // a very lenient equals() + } + } + return Stream.of( + "World", + new NonSerializable(), + new BigDecimal("123.456"), + // LOG4J2-3680 + new RuntimeException(), + null); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final Object arg) { + final Message expected = new ObjectMessage(arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(ObjectMessage.class); + assertThat(actual).isEqualTo(expected); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java new file mode 100644 index 00000000000..e4988f41417 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +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.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link ParameterFormatter}. + */ +@UsingStatusListener +class ParameterFormatterTest { + + final ListStatusListener statusListener; + + ParameterFormatterTest(ListStatusListener statusListener) { + this.statusListener = statusListener; + } + + @ParameterizedTest + @CsvSource({ + "0,,false,", + "0,,false,aaa", + "0,,true,\\{}", + "1,0,false,{}", + "1,0,true,{}\\{}", + "1,2,true,\\\\{}", + "2,8:10,true,foo \\{} {}{}", + "2,8:10,true,foo {\\} {}{}", + "2,0:2,false,{}{}", + "3,0:2:4,false,{}{}{}", + "4,0:2:4:8,false,{}{}{}aa{}", + "4,0:2:4:10,false,{}{}{}a{]b{}", + "5,0:2:4:7:10,false,{}{}{}a{}b{}" + }) + void test_pattern_analysis( + final int placeholderCount, + final String placeholderCharIndicesString, + final boolean escapedPlaceholderFound, + final String pattern) { + MessagePatternAnalysis analysis = ParameterFormatter.analyzePattern(pattern, placeholderCount); + assertThat(analysis.placeholderCount).isEqualTo(placeholderCount); + if (placeholderCount > 0) { + final int[] placeholderCharIndices = Arrays.stream(placeholderCharIndicesString.split(":")) + .mapToInt(Integer::parseInt) + .toArray(); + assertThat(analysis.placeholderCharIndices).startsWith(placeholderCharIndices); + assertThat(analysis.escapedCharFound).isEqualTo(escapedPlaceholderFound); + } + } + + @ParameterizedTest + @CsvSource({"2,pan {} {},a,pan a {}", "3,pan {}{}{},a b,pan ab{}", "1,pan {},a b c,pan a"}) + void format_should_warn_on_insufficient_args( + final int placeholderCount, final String pattern, final String argsStr, final String expectedMessage) { + final String[] args = argsStr.split(" "); + final int argCount = args.length; + + String actualMessage = ParameterFormatter.format(pattern, args, argCount); + assertThat(actualMessage).isEqualTo(expectedMessage); + final List statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(1); + final StatusData statusData = statusDataList.get(0); + assertThat(statusData.getLevel()).isEqualTo(Level.WARN); + assertThat(statusData.getMessage().getFormattedMessage()) + .isEqualTo( + "found %d argument placeholders, but provided %d for pattern `%s`", + placeholderCount, argCount, pattern); + } + + @ParameterizedTest + @MethodSource("messageFormattingTestCases") + void format_should_work( + final String pattern, final Object[] args, final int argCount, final String expectedFormattedMessage) { + final String actualFormattedMessage = ParameterFormatter.format(pattern, args, argCount); + assertThat(actualFormattedMessage).isEqualTo(expectedFormattedMessage); + } + + static Object[][] messageFormattingTestCases() { + return new Object[][] { + new Object[] {"Test message {}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message ab c"}, + new Object[] { + "Test message {} {} {} {} {} {}", + new Object[] {"a", null, "c", null, null, null}, + 6, + "Test message a null c null null null" + }, + new Object[] { + "Test message {}{} {}", + new Object[] {"a", "b", "c", "unnecessary", "superfluous"}, + 5, + "Test message ab c" + }, + new Object[] {"Test message \\{}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message {}a b"}, + new Object[] {"Test message {}{} {}\\", new Object[] {"a", "b", "c"}, 3, "Test message ab c\\"}, + new Object[] {"Test message {}{} {}\\\\", new Object[] {"a", "b", "c"}, 3, "Test message ab c\\"}, + new Object[] {"Test message \\\\{}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message \\ab c"}, + new Object[] {"Test message {}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message ab c"}, + new Object[] { + "Test message {} {} {} {} {} {}", + new Object[] {"a", null, "c", null, null, null}, + 6, + "Test message a null c null null null" + }, + new Object[] { + "Test message {}{} {}", + new Object[] {"a", "b", "c", "unnecessary", "superfluous"}, + 5, + "Test message ab c" + }, + new Object[] {"Test message \\{}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message {}a b"}, + new Object[] {"Test message {}{} {}\\", new Object[] {"a", "b", "c"}, 3, "Test message ab c\\"}, + new Object[] {"Test message {}{} {}\\\\", new Object[] {"a", "b", "c"}, 3, "Test message ab c\\"}, + new Object[] {"Test message \\\\{}{} {}", new Object[] {"a", "b", "c"}, 3, "Test message \\ab c"}, + new Object[] {"foo \\\\\\{} {}", new Object[] {"bar"}, 1, "foo \\{} bar"}, + new Object[] {"missing arg {} {}", new Object[] {1, 2}, 1, "missing arg 1 {}"}, + new Object[] {"foo {\\} {}", new Object[] {"bar"}, 1, "foo {\\} bar"} + }; + } + + @Test + 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)); + assertThat(actual).isEqualTo(expected); + } + + @Test + void testDeepToString() { + final List list = new ArrayList<>(); + list.add(1); + // noinspection CollectionAddedToSelf + list.add(list); + list.add(2); + final String actual = ParameterFormatter.deepToString(list); + final String expected = "[1, [..." + ParameterFormatter.identityToString(list) + "...], 2]"; + assertThat(actual).isEqualTo(expected); + } + + @Test + 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]"; + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("deepToStringArgumentsPrimitiveArrays") + void testDeepToStringPrimitiveArrays(Object obj, String expected) { + final String actual = ParameterFormatter.deepToString(obj); + assertThat(actual).isEqualTo(expected); + } + + static Stream deepToStringArgumentsPrimitiveArrays() { + return Stream.of( + Arguments.of(new byte[] {0, 1, 2, 3, 4}, "[0, 1, 2, 3, 4]"), + Arguments.of(new short[] {0, 1, 2, 3, 4}, "[0, 1, 2, 3, 4]"), + Arguments.of(new int[] {0, 1, 2, 3, 4}, "[0, 1, 2, 3, 4]"), + Arguments.of(new long[] {0, 1, 2, 3, 4}, "[0, 1, 2, 3, 4]"), + Arguments.of(new float[] {0, 1, 2, 3, 4}, "[0.0, 1.0, 2.0, 3.0, 4.0]"), + Arguments.of(new double[] {0, 1, 2, 3, 4}, "[0.0, 1.0, 2.0, 3.0, 4.0]"), + Arguments.of(new char[] {'a', 'b', 'c'}, "[a, b, c]"), + Arguments.of(new boolean[] {false, true}, "[false, true]")); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingTestBase.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingTestBase.java new file mode 100644 index 00000000000..072696029a7 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingTestBase.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.message; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.util.Constants; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ParameterizedMessage#getFormattedMessage()} when formatted arguments causes another {@code ParameterizedMessage#getFormattedMessage()} (i.e., recursive) invocation. + */ +abstract class ParameterizedMessageRecursiveFormattingTestBase { + + private final boolean threadLocalsEnabled; + + ParameterizedMessageRecursiveFormattingTestBase(boolean threadLocalsEnabled) { + this.threadLocalsEnabled = threadLocalsEnabled; + } + + @Test + void thread_locals_toggle_should_match() { + assertThat(Constants.ENABLE_THREADLOCALS).isEqualTo(threadLocalsEnabled); + } + + @Test + void recursion_should_not_corrupt_formatting() { + final Object argInvokingParameterizedMessageFormatting = new Object() { + @Override + public String toString() { + return new ParameterizedMessage("bar {}", "baz").getFormattedMessage(); + } + }; + final ParameterizedMessage message = + new ParameterizedMessage("foo {}", argInvokingParameterizedMessageFormatting); + final String actualText = message.getFormattedMessage(); + assertThat(actualText).isEqualTo("foo bar baz"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithThreadLocalsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithThreadLocalsTest.java new file mode 100644 index 00000000000..6496a5f12f2 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithThreadLocalsTest.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.message; + +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * {@link ParameterizedMessageRecursiveFormattingTestBase} subclass with thread locals disabled, i.e, with {@link StringBuilder} recycling. + */ +@SetSystemProperty(key = "log4j2.enable.threadlocals", value = "true") +class ParameterizedMessageRecursiveFormattingWithThreadLocalsTest + extends ParameterizedMessageRecursiveFormattingTestBase { + + ParameterizedMessageRecursiveFormattingWithThreadLocalsTest() { + super(true); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithoutThreadLocalsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithoutThreadLocalsTest.java new file mode 100644 index 00000000000..456d271d872 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageRecursiveFormattingWithoutThreadLocalsTest.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.message; + +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * {@link ParameterizedMessageRecursiveFormattingTestBase} subclass with thread locals disabled, i.e, no {@link StringBuilder} recycling. + */ +@SetSystemProperty(key = "log4j2.enable.threadlocals", value = "false") +class ParameterizedMessageRecursiveFormattingWithoutThreadLocalsTest + extends ParameterizedMessageRecursiveFormattingTestBase { + + ParameterizedMessageRecursiveFormattingWithoutThreadLocalsTest() { + super(false); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java new file mode 100644 index 00000000000..4bd5df91bef --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; +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.status.StatusData; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@UsingStatusListener +class ParameterizedMessageTest { + + final ListStatusListener statusListener; + + ParameterizedMessageTest(ListStatusListener statusListener) { + this.statusListener = statusListener; + } + + @Test + void testNoArgs() { + final String testMsg = "Test message {}"; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testZeroLength() { + final String testMsg = ""; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testOneCharLength() { + final String testMsg = "d"; + ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[] {"arg"}); + String result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + final Object[] array = null; + msg = new ParameterizedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertThat(result).isEqualTo(testMsg); + } + + @Test + void testFormat3StringArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c"); + } + + @Test + void testFormatNullArgs() { + final String testMsg = "Test message {} {} {} {} {} {}"; + final String[] args = {"a", null, "c", null, null, null}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message a null c null null null"); + } + + @Test + void testFormatStringArgsIgnoresSuperfluousArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c", "unnecessary", "superfluous"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c"); + } + + @Test + void testFormatStringArgsWithEscape() { + final String testMsg = "Test message \\{}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message {}a b"); + } + + @Test + void testFormatStringArgsWithTrailingEscape() { + final String testMsg = "Test message {}{} {}\\"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c\\"); + } + + @Test + void testFormatStringArgsWithTrailingText() { + final String testMsg = "Test message {}{} {}Text"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab cText"); + } + + @Test + void testFormatStringArgsWithTrailingEscapedEscape() { + final String testMsg = "Test message {}{} {}\\\\"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message ab c\\"); + } + + @Test + void testFormatStringArgsWithEscapedEscape() { + final String testMsg = "Test message \\\\{}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = ParameterizedMessage.format(testMsg, args); + assertThat(result).isEqualTo("Test message \\ab c"); + } + + @Test + void testSafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message {}"; + final Mutable param = new Mutable().set("abc"); + final ParameterizedMessage msg = new ParameterizedMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertThat(actual).isEqualTo("Test message XYZ").as("Should use current param value"); + + // modify parameter after calling msg.getFormattedMessage + param.set("000"); + final String after = msg.getFormattedMessage(); + assertThat(after).isEqualTo("Test message XYZ").as("Should not change after rendered once"); + } + + static Stream testSerializable() { + @SuppressWarnings("EqualsHashCode") + class NonSerializable { + @Override + public boolean equals(final Object other) { + return other instanceof NonSerializable; // a very lenient equals() + } + } + return Stream.of( + "World", + new NonSerializable(), + new BigDecimal("123.456"), + // LOG4J2-3680 + new RuntimeException(), + null); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final Object arg) { + final Message expected = new ParameterizedMessage("Hello {}!", arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(ParameterizedMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } + + /** + * In this test cases, constructed the following scenarios:
+ *

+ * 1. The arguments contains an exception, and the count of placeholder is equal to arguments include exception.
+ * 2. The arguments contains an exception, and the count of placeholder is equal to arguments except exception.
+ * All of these should not logged in status logger. + *

+ * + * @return Streams + */ + static Stream testCasesWithExceptionArgsButNoWarn() { + return Stream.of( + new Object[] { + "with exception {} {}", + new Object[] {"a", new RuntimeException()}, + "with exception a java.lang.RuntimeException" + }, + new Object[] { + "with exception {} {}", new Object[] {"a", "b", new RuntimeException()}, "with exception a b" + }); + } + + @ParameterizedTest + @MethodSource("testCasesWithExceptionArgsButNoWarn") + void formatToWithExceptionButNoWarn(final String pattern, final Object[] args, final String expected) { + final ParameterizedMessage message = new ParameterizedMessage(pattern, args); + final StringBuilder buffer = new StringBuilder(); + message.formatTo(buffer); + assertThat(buffer.toString()).isEqualTo(expected); + final List statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(0); + } + + @ParameterizedTest + @MethodSource("testCasesWithExceptionArgsButNoWarn") + void formatWithExceptionButNoWarn(final String pattern, final Object[] args, final String expected) { + final String message = ParameterizedMessage.format(pattern, args); + assertThat(message).isEqualTo(expected); + final List statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(0); + } + + /** + * In this test cases, constructed the following scenarios:
+ *

+ * 1. The placeholders are greater than the count of arguments.
+ * 2. The placeholders are less than the count of arguments.
+ * 3. The arguments contains an exception, and the placeholder is greater than normal arguments.
+ * 4. The arguments contains an exception, and the placeholder is less than the arguments.
+ * All of these should logged in status logger with WARN level. + *

+ * + * @return streams + */ + static Stream testCasesForInsufficientFormatArgs() { + return Stream.of( + new Object[] {"more {} {}", 2, new Object[] {"a"}, "more a {}"}, + new Object[] {"more {} {} {}", 3, new Object[] {"a"}, "more a {} {}"}, + new Object[] {"less {}", 1, new Object[] {"a", "b"}, "less a"}, + new Object[] {"less {} {}", 2, new Object[] {"a", "b", "c"}, "less a b"}, + new Object[] { + "more throwable {} {} {}", + 3, + new Object[] {"a", new RuntimeException()}, + "more throwable a java.lang.RuntimeException {}" + }, + new Object[] { + "less throwable {}", 1, new Object[] {"a", "b", new RuntimeException()}, "less throwable a" + }); + } + + @ParameterizedTest + @MethodSource("testCasesForInsufficientFormatArgs") + void formatToShouldWarnOnInsufficientArgs( + final String pattern, final int placeholderCount, final Object[] args, final String expected) { + final int argCount = args == null ? 0 : args.length; + verifyFormattingFailureOnInsufficientArgs(pattern, placeholderCount, argCount, expected, () -> { + final ParameterizedMessage message = new ParameterizedMessage(pattern, args); + final StringBuilder buffer = new StringBuilder(); + message.formatTo(buffer); + return buffer.toString(); + }); + } + + @ParameterizedTest + @MethodSource("testCasesForInsufficientFormatArgs") + void formatShouldWarnOnInsufficientArgs( + final String pattern, final int placeholderCount, final Object[] args, final String expected) { + final int argCount = args == null ? 0 : args.length; + verifyFormattingFailureOnInsufficientArgs( + pattern, placeholderCount, argCount, expected, () -> ParameterizedMessage.format(pattern, args)); + } + + private void verifyFormattingFailureOnInsufficientArgs( + final String pattern, + final int placeholderCount, + final int argCount, + final String expected, + final Supplier formattedMessageSupplier) { + + // Verify the formatted message + final String formattedMessage = formattedMessageSupplier.get(); + assertThat(formattedMessage).isEqualTo(expected); + + // Verify the status logger warn + final List statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).hasSize(1); + final StatusData statusData = statusDataList.get(0); + assertThat(statusData.getLevel()).isEqualTo(Level.WARN); + assertThat(statusData.getMessage().getFormattedMessage()) + .isEqualTo( + "found %d argument placeholders, but provided %d for pattern `%s`", + placeholderCount, argCount, pattern); + assertThat(statusData.getThrowable()).isNull(); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java new file mode 100644 index 00000000000..c79f459a9ac --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +/** + * Tests the ReusableMessageFactory class. + */ +class ReusableMessageFactoryTest { + + @Test + void testCreateEventReturnsDifferentInstanceIfNotReleased() { + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); + final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); + assertNotSame(message1, message2); + ReusableMessageFactory.release(message1); + ReusableMessageFactory.release(message2); + } + + @Test + void testCreateEventReturnsSameInstance() { + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); + + ReusableMessageFactory.release(message1); + final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); + assertSame(message1, message2); + + ReusableMessageFactory.release(message2); + final Message message3 = factory.newMessage("text, AAA={} BBB={} p2={} p3={}", 9, 8, 7, 6); + assertSame(message2, message3); + ReusableMessageFactory.release(message3); + } + + private void assertReusableParameterizeMessage(final Message message, final String txt, final Object[] params) { + assertInstanceOf(ReusableParameterizedMessage.class, message); + final ReusableParameterizedMessage msg = (ReusableParameterizedMessage) message; + assertTrue(msg.reserved, "reserved"); + + assertEquals(txt, msg.getFormat()); + assertEquals(msg.getParameterCount(), params.length, "count"); + final Object[] messageParams = msg.getParameters(); + for (int i = 0; i < params.length; i++) { + assertEquals(messageParams[i], params[i]); + } + } + + @Test + void testCreateEventOverwritesFields() { + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); + assertReusableParameterizeMessage(message1, "text, p0={} p1={} p2={} p3={}", new Object[] { + new Integer(1), // + new Integer(2), // + new Integer(3), // + new Integer(4), // + }); + + ReusableMessageFactory.release(message1); + final Message message2 = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4); + assertReusableParameterizeMessage(message1, "other, A={} B={} C={} D={}", new Object[] { + new Integer(1), // + new Integer(2), // + new Integer(3), // + new Integer(4), // + }); + assertSame(message1, message2); + ReusableMessageFactory.release(message2); + } + + @Test + void testCreateEventReturnsThreadLocalInstance() throws Exception { + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final AtomicReference message1 = new AtomicReference<>(); + final AtomicReference message2 = new AtomicReference<>(); + final Thread t1 = new Thread("THREAD 1") { + @Override + public void run() { + message1.set(factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4)); + } + }; + final Thread t2 = new Thread("Thread 2") { + @Override + public void run() { + message2.set(factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4)); + } + }; + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertNotNull(message1.get()); + assertNotNull(message2.get()); + assertNotSame(message1.get(), message2.get()); + assertReusableParameterizeMessage(message1.get(), "text, p0={} p1={} p2={} p3={}", new Object[] { + new Integer(1), // + new Integer(2), // + new Integer(3), // + new Integer(4), // + }); + + assertReusableParameterizeMessage(message2.get(), "other, A={} B={} C={} D={}", new Object[] { + new Integer(1), // + new Integer(2), // + new Integer(3), // + new Integer(4), // + }); + ReusableMessageFactory.release(message1.get()); + ReusableMessageFactory.release(message2.get()); + } + + @Test + void canSerializeRoundTrip() throws IOException { + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (final ObjectOutputStream out = new ObjectOutputStream(bytes)) { + out.writeObject(factory); + } + try (final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + assertDoesNotThrow(in::readObject); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java new file mode 100644 index 00000000000..a7d5e8ce1ee --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests ReusableObjectMessage. + */ +class ReusableObjectMessageTest { + + @Test + void testSet_InitializesFormattedMessage() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormattedMessage()); + } + + @Test + void testGetFormattedMessage_InitiallyNullString() { + assertEquals("null", new ReusableObjectMessage().getFormattedMessage()); + } + + @Test + void testGetFormattedMessage_ReturnsLatestSetString() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormattedMessage()); + msg.set("def"); + assertEquals("def", msg.getFormattedMessage()); + msg.set("xyz"); + assertEquals("xyz", msg.getFormattedMessage()); + } + + @Test + void testGetFormat_InitiallyNull() { + assertNull(new ReusableObjectMessage().getFormat()); + } + + @Test + void testGetFormat_ReturnsLatestSetString() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormat()); + msg.set("def"); + assertEquals("def", msg.getFormat()); + msg.set("xyz"); + assertEquals("xyz", msg.getFormat()); + } + + @Test + void testGetParameters_InitiallyReturnsNullObjectInLength1Array() { + assertArrayEquals(new Object[] {null}, new ReusableObjectMessage().getParameters()); + } + + @Test + void testGetParameters_ReturnsSetObjectInParameterArrayAfterMessageSet() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + msg.set("abc"); + assertArrayEquals(new Object[] {"abc"}, msg.getParameters()); + msg.set("def"); + assertArrayEquals(new Object[] {"def"}, msg.getParameters()); + } + + @Test + void testGetThrowable_InitiallyReturnsNull() { + assertNull(new ReusableObjectMessage().getThrowable()); + } + + @Test + void testGetThrowable_ReturnsNullAfterMessageSet() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + msg.set("abc"); + assertNull(msg.getThrowable()); + msg.set("def"); + assertNull(msg.getThrowable()); + } + + @Test + void testFormatTo_InitiallyWritesNull() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + final StringBuilder sb = new StringBuilder(); + msg.formatTo(sb); + assertEquals("null", sb.toString()); + } + + @Test + void testFormatTo_WritesLatestSetString() { + final ReusableObjectMessage msg = new ReusableObjectMessage(); + final StringBuilder sb = new StringBuilder(); + msg.formatTo(sb); + assertEquals("null", sb.toString()); + sb.setLength(0); + msg.set("abc"); + msg.formatTo(sb); + assertEquals("abc", sb.toString()); + sb.setLength(0); + msg.set("def"); + msg.formatTo(sb); + assertEquals("def", sb.toString()); + sb.setLength(0); + msg.set("xyz"); + msg.formatTo(sb); + assertEquals("xyz", sb.toString()); + } + + static Stream testSerializable() { + return ObjectMessageTest.testSerializable(); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final Object arg) { + final ReusableObjectMessage expected = new ReusableObjectMessage(); + expected.set(arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(ObjectMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java new file mode 100644 index 00000000000..fc7e30aa607 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests ReusableParameterizedMessage. + */ +public class ReusableParameterizedMessageTest { + + public static ReusableParameterizedMessage set( + final ReusableParameterizedMessage msg, final String format, final Object... params) { + + return msg.set(format, params); + } + + @Test + void testNoArgs() { + final String testMsg = "Test message {}"; + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + msg.set(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + assertEquals(testMsg, result); + + msg.set(testMsg, (Object) null); + result = msg.getFormattedMessage(); + assertEquals("Test message null", result); + + msg.set(testMsg, null, null); + result = msg.getFormattedMessage(); + assertEquals("Test message null", result); + } + + @Test + void testFormat3StringArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message ab c", result); + } + + @Test + void testFormatNullArgs() { + final String testMsg = "Test message {} {} {} {} {} {}"; + final String[] args = {"a", null, "c", null, null, null}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message a null c null null null", result); + } + + @Test + void testFormatStringArgsIgnoresSuperfluousArgs() { + final String testMsg = "Test message {}{} {}"; + final String[] args = {"a", "b", "c", "unnecessary", "superfluous"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message ab c", result); + } + + @Test + void testFormatStringArgsWithEscape() { + final String testMsg = "Test message \\{}{} {}"; + final String[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message {}a b", result); + } + + @Test + void testFormatStringArgsWithTrailingEscape() { + final String testMsg = "Test message {}{} {}\\"; + final String[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message ab c\\", result); + } + + @Test + void testFormatStringArgsWithTrailingText() { + final String testMsg = "Test message {}{} {}Text"; + final String[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message ab cText", result); + } + + @Test + void testFormatStringArgsWithTrailingEscapedEscape() { + final String testMsg = "Test message {}{} {}\\\\"; + final String[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); + assertEquals("Test message ab c\\", result); + } + + @Test + void testFormatStringArgsWithEscapedEscape() { + final String testMsg = "Test message \\\\{}{} {}"; + final Object[] args = {"a", "b", "c"}; + final String result = + new ReusableParameterizedMessage().set(testMsg, args).getFormattedMessage(); + assertEquals("Test message \\ab c", result); + } + + @Test + void testNotSafeWithMutableParams() { + final String testMsg = "Test message {}"; + final Mutable param = new Mutable().set("abc"); + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + msg.set(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + 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("Test message 000", after, "Renders again"); + } + + @Test + void testThrowable() { + final String testMsg = "Test message {}"; + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + final Throwable EXCEPTION1 = new IllegalAccessError("#1"); + msg.set(testMsg, "msg", EXCEPTION1); + assertSame(EXCEPTION1, msg.getThrowable()); + + final Throwable EXCEPTION2 = new UnsupportedOperationException("#2"); + msg.set(testMsg, "msgs", EXCEPTION2); + assertSame(EXCEPTION2, msg.getThrowable()); + } + + @Test + void testParameterConsumer() { + final String testMsg = "Test message {}"; + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + final Throwable EXCEPTION1 = new IllegalAccessError("#1"); + msg.set(testMsg, "msg", EXCEPTION1); + final List expected = new LinkedList<>(); + expected.add("msg"); + expected.add(EXCEPTION1); + final List actual = new LinkedList<>(); + msg.forEachParameter((parameter, parameterIndex, state) -> actual.add(parameter), null); + assertEquals(expected, actual); + } + + static Stream testSerializable() { + return Stream.of("World", new Object(), null); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final Object arg) { + final ReusableParameterizedMessage expected = new ReusableParameterizedMessage(); + expected.set("Hello {}!", arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(ParameterizedMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java new file mode 100644 index 00000000000..637f6d48c32 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.apache.logging.log4j.util.Constants; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests ReusableSimpleMessage. + */ +class ReusableSimpleMessageTest { + + @Test + void testSet_InitializesFormattedMessage() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormattedMessage()); + } + + @Test + void testGetFormattedMessage_InitiallyStringNull() { + assertEquals("null", new ReusableSimpleMessage().getFormattedMessage()); + } + + @Test + void testGetFormattedMessage_ReturnsLatestSetString() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormattedMessage()); + msg.set("def"); + assertEquals("def", msg.getFormattedMessage()); + msg.set("xyz"); + assertEquals("xyz", msg.getFormattedMessage()); + } + + @Test + void testGetFormat_InitiallyStringNull() { + assertNull(new ReusableSimpleMessage().getFormat()); + } + + @Test + void testGetFormat_ReturnsLatestSetString() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + msg.set("abc"); + assertEquals("abc", msg.getFormat()); + msg.set("def"); + assertEquals("def", msg.getFormat()); + msg.set("xyz"); + assertEquals("xyz", msg.getFormat()); + } + + @Test + void testGetParameters_InitiallyReturnsEmptyArray() { + assertArrayEquals(Constants.EMPTY_OBJECT_ARRAY, new ReusableSimpleMessage().getParameters()); + } + + @Test + void testGetParameters_ReturnsEmptyArrayAfterMessageSet() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + msg.set("abc"); + assertArrayEquals(Constants.EMPTY_OBJECT_ARRAY, msg.getParameters()); + msg.set("def"); + assertArrayEquals(Constants.EMPTY_OBJECT_ARRAY, msg.getParameters()); + } + + @Test + void testGetThrowable_InitiallyReturnsNull() { + assertNull(new ReusableSimpleMessage().getThrowable()); + } + + @Test + void testGetThrowable_ReturnsNullAfterMessageSet() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + msg.set("abc"); + assertNull(msg.getThrowable()); + msg.set("def"); + assertNull(msg.getThrowable()); + } + + @Test + void testFormatTo_InitiallyWritesNull() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + final StringBuilder sb = new StringBuilder(); + msg.formatTo(sb); + assertEquals("null", sb.toString()); + } + + @Test + void testFormatTo_WritesLatestSetString() { + final ReusableSimpleMessage msg = new ReusableSimpleMessage(); + final StringBuilder sb = new StringBuilder(); + msg.formatTo(sb); + assertEquals("null", sb.toString()); + sb.setLength(0); + msg.set("abc"); + msg.formatTo(sb); + assertEquals("abc", sb.toString()); + sb.setLength(0); + msg.set("def"); + msg.formatTo(sb); + assertEquals("def", sb.toString()); + sb.setLength(0); + msg.set("xyz"); + msg.formatTo(sb); + assertEquals("xyz", sb.toString()); + } + + static Stream testSerializable() { + return SimpleMessageTest.testSerializable(); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final CharSequence arg) { + final ReusableSimpleMessage expected = new ReusableSimpleMessage(); + expected.set(arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(SimpleMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java new file mode 100644 index 00000000000..a97367a6a23 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.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.message; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests the SimpleMessage class. + */ +class SimpleMessageTest { + @Test + void formatTo_usesCachedMessageString() { + final StringBuilder charSequence = new StringBuilder("initial value"); + final SimpleMessage message = new SimpleMessage(charSequence); + assertEquals("initial value", message.getFormattedMessage()); + + charSequence.setLength(0); + charSequence.append("different value"); + + final StringBuilder result = new StringBuilder(); + message.formatTo(result); + assertEquals("initial value", result.toString()); + } + + static Stream testSerializable() { + class NonSerializable implements CharSequence { + + private final CharSequence value; + + public NonSerializable(final CharSequence value) { + this.value = value; + } + + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int index) { + return value.charAt(index); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public CharSequence subSequence(int start, int end) { + return value.subSequence(start, end); + } + } + return Stream.of("World", new NonSerializable("World2"), null); + } + + @ParameterizedTest + @MethodSource + void testSerializable(final CharSequence arg) { + final Message expected = new SimpleMessage(arg); + final Message actual = SerialUtil.deserialize(SerialUtil.serialize(expected)); + assertThat(actual).isInstanceOf(SimpleMessage.class); + assertThat(actual.getFormattedMessage()).isEqualTo(expected.getFormattedMessage()); + } +} 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..29309b36592 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.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.logging.log4j.message; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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.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; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +class StringFormattedMessageTest { + + private static final int LOOP_CNT = 500; + String[] array = new String[LOOP_CNT]; + + @Test + 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 + 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 + 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 + 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 + 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 + 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 + 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"); + } + + @Test + 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 + 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-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java new file mode 100644 index 00000000000..f02d95eefef --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java @@ -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. + */ +package org.apache.logging.log4j.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * + */ +class StructuredDataMessageTest { + + @Test + void testMsg() { + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); + final String result = msg.getFormattedMessage(); + final String expected = + "Alert [MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; + assertEquals(expected, result); + } + + @Test + void testMsgNonFull() { + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); + final String result = msg.getFormattedMessage(new String[] {"WHATEVER"}); + final String expected = + "[MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"]"; + assertEquals(expected, result); + } + + @Test + void testMsgXml() { + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); + final String result = msg.getFormattedMessage(new String[] {"XML"}); + final String expected = "\n" + + "Alert\n" + + "MsgId@12345\n" + + "\n" + + " This is a very long test memo to prevent regression of LOG4J2-114\n" + + " Test message {}\n" + + " Log4j\n" + + "\n" + + "\n"; + assertEquals(expected, result); + } + + @Test + void testBuilder() { + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert") + .with("message", testMsg) + .with("project", "Log4j") + .with("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); + final String result = msg.getFormattedMessage(); + final String expected = + "Alert [MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; + assertEquals(expected, result); + } + + @Test + void testMsgWithKeyTooLong() { + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); + assertThrows( + IllegalArgumentException.class, + () -> msg.put("This is a very long key that will violate the key length validation", "Testing")); + } + + @Test + void testMutableByDesign() { // LOG4J2-763 + final String testMsg = "Test message {}"; + final StructuredDataMessage msg = new StructuredDataMessage("MsgId@1", testMsg, "Alert"); + + // modify parameter before calling msg.getFormattedMessage + msg.put("message", testMsg); + msg.put("project", "Log4j"); + final String result = msg.getFormattedMessage(); + final String expected = "Alert [MsgId@1 message=\"Test message {}\" project=\"Log4j\"] Test message {}"; + assertEquals(expected, result); + + // modify parameter after calling msg.getFormattedMessage + msg.put("memo", "Added later"); + final String result2 = msg.getFormattedMessage(); + final String expected2 = + "Alert [MsgId@1 memo=\"Added later\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; + assertEquals(expected2, result2); + } + + @Test + 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-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java new file mode 100644 index 00000000000..6c1e4508dff --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.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.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import org.junit.jupiter.api.Test; + +class ThreadDumpMessageTest { + + @Test + void testMessage() { + final ThreadDumpMessage msg = new ThreadDumpMessage("Testing"); + + final String message = msg.getFormattedMessage(); + // System.out.print(message); + assertTrue(message.contains("Testing"), "No header"); + assertTrue(message.contains("RUNNABLE"), "No RUNNABLE"); + assertTrue(message.contains("ThreadDumpMessage"), "No ThreadDumpMessage"); + } + + @Test + void testMessageWithLocks() throws Exception { + final ReentrantLock lock = new ReentrantLock(); + lock.lock(); + final Thread thread1 = new Thread1(lock); + thread1.start(); + ThreadDumpMessage msg; + synchronized (this) { + final Thread thread2 = new Thread2(this); + thread2.start(); + try { + Thread.sleep(200); + msg = new ThreadDumpMessage("Testing"); + } finally { + lock.unlock(); + } + } + + final String message = msg.getFormattedMessage(); + // System.out.print(message); + 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")); + } + + @Test + void testToString() { + final ThreadDumpMessage msg = new ThreadDumpMessage("Test"); + final String actual = msg.toString(); + assertTrue(actual.contains("Test")); + assertTrue(actual.contains("RUNNABLE")); + assertTrue(actual.contains(getClass().getName())); + } + + @Test + void testUseConstructorThread() throws InterruptedException { // LOG4J2-763 + final ThreadDumpMessage msg = new ThreadDumpMessage("Test"); + + final AtomicReference actual = new AtomicReference<>(); + final Thread other = new Thread("OtherThread") { + @Override + public void run() { + actual.set(msg.getFormattedMessage()); + } + }; + other.start(); + other.join(); + + assertFalse(actual.get().contains("OtherThread"), "No mention of other thread in msg"); + } + + @Test + void formatTo_usesCachedMessageString() throws Exception { + + final ThreadDumpMessage message = new ThreadDumpMessage(""); + final String initial = message.getFormattedMessage(); + assertFalse(initial.contains("ThreadWithCountDownLatch"), "no ThreadWithCountDownLatch thread yet"); + + final CountDownLatch started = new CountDownLatch(1); + final CountDownLatch keepAlive = new CountDownLatch(1); + final ThreadWithCountDownLatch thread = new ThreadWithCountDownLatch(started, keepAlive); + thread.start(); + started.await(); // ensure thread is running + + final StringBuilder result = new StringBuilder(); + message.formatTo(result); + assertFalse(result.toString().contains("ThreadWithCountDownLatch"), "no ThreadWithCountDownLatch captured"); + assertEquals(initial, result.toString()); + keepAlive.countDown(); // allow thread to die + } + + private static class Thread1 extends Thread { + private final ReentrantLock lock; + + public Thread1(final ReentrantLock lock) { + this.lock = lock; + } + + @Override + public void run() { + lock.lock(); + lock.unlock(); + } + } + + private static class Thread2 extends Thread { + private final Object obj; + + public Thread2(final Object obj) { + this.obj = obj; + } + + @Override + public void run() { + synchronized (obj) { + } + } + } + + private static class ThreadWithCountDownLatch extends Thread { + private final CountDownLatch started; + private final CountDownLatch keepAlive; + volatile boolean finished; + + public ThreadWithCountDownLatch(final CountDownLatch started, final CountDownLatch keepAlive) { + super("ThreadWithCountDownLatch"); + this.started = started; + this.keepAlive = keepAlive; + setDaemon(true); + } + + @Override + public void run() { + started.countDown(); + try { + keepAlive.await(); + } catch (final InterruptedException e) { + // ignored + } + finished = true; + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java new file mode 100644 index 00000000000..c6ba817dc61 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.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.simple; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.test.junit.LoggerContextFactoryExtension; +import org.apache.logging.log4j.util.Constants; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@Tag("smoke") +class SimpleLoggerTest { + + @RegisterExtension + public static final LoggerContextFactoryExtension EXTENSION = + new LoggerContextFactoryExtension(SimpleLoggerContextFactory.INSTANCE); + + private final Logger logger = LogManager.getLogger("TestError"); + + @Test + void testString() { + logger.error("Logging without args"); + } + + @Test + void testMissingMessageArg() { + logger.error("Logging without args {}"); + } + + @Test + void testEmptyObjectArray() { + logger.error(Constants.EMPTY_OBJECT_ARRAY); + } + + /** + * Tests LOG4J2-811. + */ + @Test + void testMessageWithEmptyObjectArray() { + logger.error("Logging with an empty Object[] {} {}", Constants.EMPTY_BYTE_ARRAY); + } + + /** + * Tests LOG4J2-811. + */ + @Test + void testMessageWithShortArray() { + logger.error("Logging with a size 1 Object[] {} {}", new Object[] {"only one param"}); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java new file mode 100644 index 00000000000..4e3356aad31 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.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.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.test.spi.ThreadContextMapSuite; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; + +/** + * Tests the {@code DefaultThreadContextMap} class. + */ +@UsingThreadContextMap +class DefaultThreadContextMapTest extends ThreadContextMapSuite { + + private ThreadContextMap createThreadContextMap() { + return new DefaultThreadContextMap(); + } + + private ThreadContextMap createInheritableThreadContextMap() { + final Properties props = new Properties(); + props.setProperty("log4j2.isThreadContextMapInheritable", "true"); + final PropertiesUtil util = new PropertiesUtil(props); + return new DefaultThreadContextMap(util); + } + + @Test + void singleValue() { + singleValue(createThreadContextMap()); + } + + @Test + void testPutAll() { + final DefaultThreadContextMap map = new DefaultThreadContextMap(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + final int mapSize = 10; + final Map newMap = new HashMap<>(mapSize); + for (int i = 1; i <= mapSize; i++) { + newMap.put("key" + i, "value" + i); + } + map.putAll(newMap); + assertFalse(map.isEmpty()); + for (int i = 1; i <= mapSize; i++) { + assertTrue(map.containsKey("key" + i)); + assertEquals("value" + i, map.get("key" + i)); + } + } + + @Test + void testClear() { + final DefaultThreadContextMap map = createMap(); + + map.clear(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + assertFalse(map.containsKey("key2")); + } + + private DefaultThreadContextMap createMap() { + final DefaultThreadContextMap map = new DefaultThreadContextMap(); + assertTrue(map.isEmpty()); + map.put("key", "value"); + map.put("key2", "value2"); + assertEquals("value", map.get("key")); + assertEquals("value2", map.get("key2")); + return map; + } + + @Test + void getCopyReturnsMutableCopy() { + getCopyReturnsMutableCopy(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsNullIfEmpty() { + getImmutableMapReturnsNullIfEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsImmutableMapIfNonEmpty() { + getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapCopyNotAffectedByContextMapChanges() { + getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap()); + } + + @Test + void testToStringShowsMapContext() { + final DefaultThreadContextMap map = new DefaultThreadContextMap(); + assertEquals("{}", map.toString()); + + map.put("key1", "value1"); + assertEquals("{key1=value1}", map.toString()); + + map.remove("key1"); + map.put("key2", "value2"); + assertEquals("{key2=value2}", map.toString()); + } + + @Test + void threadLocalNotInheritableByDefault() { + threadLocalNotInheritableByDefault(createThreadContextMap()); + } + + @Test + void threadLocalInheritableIfConfigured() { + threadLocalInheritableIfConfigured(createInheritableThreadContextMap()); + } +} 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 77% 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..08f1755042e 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 @@ -1,40 +1,42 @@ /* * 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.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 org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; - -public class DefaultThreadContextStackTest { - - @Before - public void before() { - // clear the thread-local map - new DefaultThreadContextMap(true).clear(); - } +@UsingAnyThreadContext +class DefaultThreadContextStackTest { @Test - public void testEqualsVsSameKind() { + void testEqualsVsSameKind() { final DefaultThreadContextStack stack1 = createStack(); final DefaultThreadContextStack stack2 = createStack(); assertEquals(stack1, stack1); @@ -44,9 +46,9 @@ public void testEqualsVsSameKind() { } @Test - public void testEqualsVsMutable() { - final DefaultThreadContextStack stack1 = createStack(); - final MutableThreadContextStack stack2 = MutableThreadContextStackTest.createStack(); + void testEqualsVsMutable() { + final ThreadContextStack stack1 = createStack(); + final ThreadContextStack stack2 = MutableThreadContextStackTest.createStack(); assertEquals(stack1, stack1); assertEquals(stack2, stack2); assertEquals(stack1, stack2); @@ -54,49 +56,49 @@ public void testEqualsVsMutable() { } @Test - public void testHashCodeVsSameKind() { + void testHashCodeVsSameKind() { final DefaultThreadContextStack stack1 = createStack(); final DefaultThreadContextStack stack2 = createStack(); assertEquals(stack1.hashCode(), stack2.hashCode()); } @Test - public void testImmutableOrNullReturnsNullIfUseStackIsFalse() { + void testImmutableOrNullReturnsNullIfUseStackIsFalse() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(false); stack.clear(); - assertEquals(null, stack.getImmutableStackOrNull()); + assertNull(stack.getImmutableStackOrNull()); } @Test - public void testImmutableOrNullReturnsNullIfStackIsEmpty() { + void testImmutableOrNullReturnsNullIfStackIsEmpty() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertTrue(stack.isEmpty()); - assertEquals(null, stack.getImmutableStackOrNull()); + assertNull(stack.getImmutableStackOrNull()); } @Test - public void testImmutableOrNullReturnsCopyOfContents() { + 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) - public void testModifyingImmutableOrNullThrowsException() { + @Test + void testModifyingImmutableOrNullThrowsException() { final DefaultThreadContextStack stack = createStack(); final int originalSize = stack.size(); assertTrue(originalSize > 0); final ContextStack actual = stack.getImmutableStackOrNull(); assertEquals(originalSize, actual.size()); - actual.pop(); + assertThrows(UnsupportedOperationException.class, () -> actual.pop()); } @Test - public void testDoesNothingIfConstructedWithUseStackIsFalse() { + void testDoesNothingIfConstructedWithUseStackIsFalse() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(false); stack.clear(); assertTrue(stack.isEmpty()); @@ -108,7 +110,7 @@ public void testDoesNothingIfConstructedWithUseStackIsFalse() { } @Test - public void testPushAndAddIncreaseStack() { + void testPushAndAddIncreaseStack() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertTrue(stack.isEmpty()); @@ -119,7 +121,7 @@ public void testPushAndAddIncreaseStack() { } @Test - public void testPeekReturnsLastAddedItem() { + void testPeekReturnsLastAddedItem() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertTrue(stack.isEmpty()); @@ -134,7 +136,7 @@ public void testPeekReturnsLastAddedItem() { } @Test - public void testPopRemovesLastAddedItem() { + void testPopRemovesLastAddedItem() { final DefaultThreadContextStack stack = createStack(); assertEquals(3, stack.getDepth()); @@ -152,7 +154,7 @@ public void testPopRemovesLastAddedItem() { } @Test - public void testAsList() { + void testAsList() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertTrue(stack.isEmpty()); @@ -164,7 +166,7 @@ public void testAsList() { } @Test - public void testTrim() { + void testTrim() { final DefaultThreadContextStack stack = createStack(); stack.trim(1); @@ -173,7 +175,7 @@ public void testTrim() { } @Test - public void testCopy() { + void testCopy() { final DefaultThreadContextStack stack = createStack(); final ThreadContextStack copy = stack.copy(); @@ -203,7 +205,7 @@ public void testCopy() { } @Test - public void testClear() { + void testClear() { final DefaultThreadContextStack stack = createStack(); stack.clear(); @@ -225,7 +227,7 @@ static DefaultThreadContextStack createStack() { } @Test - public void testContains() { + void testContains() { final DefaultThreadContextStack stack = createStack(); assertTrue(stack.contains("msg1")); @@ -234,7 +236,7 @@ public void testContains() { } @Test - public void testIteratorReturnsInListOrderNotStackOrder() { + void testIteratorReturnsInListOrderNotStackOrder() { final DefaultThreadContextStack stack = createStack(); final Iterator iter = stack.iterator(); @@ -248,25 +250,25 @@ public void testIteratorReturnsInListOrderNotStackOrder() { } @Test - public void testToArray() { + void testToArray() { final DefaultThreadContextStack stack = createStack(); - final String[] expecteds = { "msg1", "msg2", "msg3" }; + final String[] expecteds = {"msg1", "msg2", "msg3"}; assertArrayEquals(expecteds, stack.toArray()); } @Test - public void testToArrayTArray() { + void testToArrayTArray() { final DefaultThreadContextStack stack = createStack(); - final String[] expecteds = { "msg1", "msg2", "msg3" }; + final String[] expecteds = {"msg1", "msg2", "msg3"}; final String[] result = new String[3]; assertArrayEquals(expecteds, stack.toArray(result)); assertSame(result, stack.toArray(result)); } @Test - public void testRemove() { + void testRemove() { final DefaultThreadContextStack stack = createStack(); assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); @@ -277,19 +279,19 @@ 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()); } @Test - public void testContainsAll() { + void testContainsAll() { final DefaultThreadContextStack stack = createStack(); assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); } @Test - public void testAddAll() { + void testAddAll() { final DefaultThreadContextStack stack = createStack(); stack.addAll(Arrays.asList("msg4", "msg5")); @@ -302,7 +304,7 @@ public void testAddAll() { } @Test - public void testRemoveAll() { + void testRemoveAll() { final DefaultThreadContextStack stack = createStack(); stack.removeAll(Arrays.asList("msg1", "msg3")); @@ -313,7 +315,7 @@ public void testRemoveAll() { } @Test - public void testRetainAll() { + void testRetainAll() { final DefaultThreadContextStack stack = createStack(); stack.retainAll(Arrays.asList("msg1", "msg3")); @@ -324,7 +326,7 @@ public void testRetainAll() { } @Test - public void testToStringShowsListContents() { + void testToStringShowsListContents() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertEquals("[]", stack.toString()); 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..a8b90c54c57 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.simple.SimpleLoggerContext; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.TestLoggerContext; +import org.apache.logging.log4j.test.TestLoggerContextFactory; +import org.junit.jupiter.api.Test; + +/** + * Created by Pavel.Sivolobtchik@uxpsystems.com on 2016-10-19. + */ +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(final String name, final LoggerContext context) { + return context.getLogger(name); + } + + @Override + protected LoggerContext getContext() { + return null; + } + + public LoggerContext getContext(final String fqcn) { + for (LoggerContext lc : registry.keySet()) { + final TestLoggerContext2 context = (TestLoggerContext2) lc; + if (fqcn.equals(context.getName())) { + return context; + } + } + final 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(final String name, final LoggerContextShutdownAware listener) { + this.name = name; + this.listener = listener; + } + + public String getName() { + return name; + } + + public void shutdown() { + listener.contextShutdown(this); + } + } + + @Test + void testCleanup() { + final LoggerContextFactory factory = new TestLoggerContextFactory(); + final TestLoggerAdapter2 adapter = new TestLoggerAdapter2(); + for (int i = 0; i < 5; ++i) { + final LoggerContext lc = adapter.getContext(Integer.toString(i)); + lc.getLogger(Integer.toString(i)); + } + assertEquals(5, adapter.registry.size(), "Expected 5 LoggerContexts"); + final 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 + 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-test/src/test/java/org/apache/logging/log4j/spi/LoggerRegistryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerRegistryTest.java new file mode 100644 index 00000000000..269785ea714 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerRegistryTest.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.spi; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.ref.WeakReference; +import java.util.stream.Stream; +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.test.TestLogger; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class LoggerRegistryTest { + + private static final String LOGGER_NAME = LoggerRegistryTest.class.getName(); + + static Stream<@Nullable MessageFactory> doesNotLoseLoggerReferences() { + return Stream.of( + ParameterizedMessageFactory.INSTANCE, + ReusableMessageFactory.INSTANCE, + new ParameterizedMessageFactory(), + null); + } + + /** + * @see ()); + assertTrue(stack.isEmpty()); + } + + @Test + void testConstructorCopiesListContents() { + final List initial = Arrays.asList("a", "b", "c"); + final MutableThreadContextStack stack = new MutableThreadContextStack(initial); + assertFalse(stack.isEmpty()); + assertTrue(stack.containsAll(initial)); + } + + @Test + void testPushAndAddIncreaseStack() { + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); + stack.clear(); + assertTrue(stack.isEmpty()); + stack.push("msg1"); + stack.add("msg2"); + + assertEquals(2, stack.size()); + } + + @Test + void testPeekReturnsLastAddedItem() { + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); + stack.clear(); + assertTrue(stack.isEmpty()); + stack.push("msg1"); + stack.add("msg2"); + + assertEquals(2, stack.size()); + assertEquals("msg2", stack.peek()); + + stack.push("msg3"); + assertEquals("msg3", stack.peek()); + } + + @Test + void testPopRemovesLastAddedItem() { + final MutableThreadContextStack stack = createStack(); + assertEquals(3, stack.getDepth()); + + assertEquals("msg3", stack.pop()); + assertEquals(2, stack.size()); + assertEquals(2, stack.getDepth()); + + assertEquals("msg2", stack.pop()); + assertEquals(1, stack.size()); + assertEquals(1, stack.getDepth()); + + assertEquals("msg1", stack.pop()); + assertEquals(0, stack.size()); + assertEquals(0, stack.getDepth()); + } + + @Test + void testAsList() { + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); + stack.clear(); + assertTrue(stack.isEmpty()); + stack.push("msg1"); + stack.add("msg2"); + stack.push("msg3"); + + assertEquals(Arrays.asList("msg1", "msg2", "msg3"), stack.asList()); + } + + @Test + void testTrim() { + final MutableThreadContextStack stack = createStack(); + + stack.trim(1); + assertEquals(1, stack.size()); + assertEquals("msg1", stack.peek()); + } + + @Test + void testCopy() { + final MutableThreadContextStack stack = createStack(); + + final ThreadContextStack copy = stack.copy(); + assertEquals(3, copy.size()); + assertTrue(copy.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); + + // clearing stack does not affect copy + stack.clear(); + assertTrue(stack.isEmpty()); + assertEquals(3, copy.size()); // not affected + assertTrue(copy.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); + + // adding to copy does not affect stack + copy.add("other"); + assertEquals(4, copy.size()); // not affected + assertTrue(stack.isEmpty()); + + // adding to stack does not affect copy + stack.push("newStackMsg"); + assertEquals(1, stack.size()); + assertEquals(4, copy.size()); // not affected + + // clearing copy does not affect stack + copy.clear(); + assertTrue(copy.isEmpty()); + assertEquals(1, stack.size()); + } + + @Test + void testClear() { + final MutableThreadContextStack stack = createStack(); + + stack.clear(); + assertTrue(stack.isEmpty()); + } + + @Test + void testEqualsVsSameKind() { + final MutableThreadContextStack stack1 = createStack(); + final MutableThreadContextStack stack2 = createStack(); + assertEquals(stack1, stack1); + assertEquals(stack2, stack2); + assertEquals(stack1, stack2); + assertEquals(stack2, stack1); + } + + @Test + void testHashCodeVsSameKind() { + final MutableThreadContextStack stack1 = createStack(); + final MutableThreadContextStack stack2 = createStack(); + assertEquals(stack1.hashCode(), stack2.hashCode()); + } + + /** + * @return + */ + static MutableThreadContextStack createStack() { + final MutableThreadContextStack stack1 = new MutableThreadContextStack(new ArrayList<>()); + stack1.clear(); + assertTrue(stack1.isEmpty()); + stack1.push("msg1"); + stack1.add("msg2"); + stack1.push("msg3"); + assertEquals(3, stack1.size()); + return stack1; + } + + @Test + void testContains() { + final MutableThreadContextStack stack = createStack(); + + assertTrue(stack.contains("msg1")); + assertTrue(stack.contains("msg2")); + assertTrue(stack.contains("msg3")); + } + + @Test + void testIteratorReturnsInListOrderNotStackOrder() { + final MutableThreadContextStack stack = createStack(); + + final Iterator iter = stack.iterator(); + assertTrue(iter.hasNext()); + assertEquals("msg1", iter.next()); + assertTrue(iter.hasNext()); + assertEquals("msg2", iter.next()); + assertTrue(iter.hasNext()); + assertEquals("msg3", iter.next()); + assertFalse(iter.hasNext()); + } + + @Test + void testToArray() { + final MutableThreadContextStack stack = createStack(); + + final String[] expecteds = {"msg1", "msg2", "msg3"}; + assertArrayEquals(expecteds, stack.toArray()); + } + + @Test + void testToArrayTArray() { + final MutableThreadContextStack stack = createStack(); + + final String[] expecteds = {"msg1", "msg2", "msg3"}; + final String[] result = new String[3]; + assertArrayEquals(expecteds, stack.toArray(result)); + assertSame(result, stack.toArray(result)); + } + + @Test + void testRemove() { + final MutableThreadContextStack stack = createStack(); + assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); + + stack.remove("msg1"); + assertEquals(2, stack.size()); + assertTrue(stack.containsAll(Arrays.asList("msg2", "msg3"))); + assertEquals("msg3", stack.peek()); + + stack.remove("msg3"); + assertEquals(1, stack.size()); + assertTrue(stack.contains("msg2")); + assertEquals("msg2", stack.peek()); + } + + @Test + void testContainsAll() { + final MutableThreadContextStack stack = createStack(); + + assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); + } + + @Test + void testAddAll() { + final MutableThreadContextStack stack = createStack(); + + stack.addAll(Arrays.asList("msg4", "msg5")); + assertEquals(5, stack.size()); + assertTrue(stack.contains("msg1")); + assertTrue(stack.contains("msg2")); + assertTrue(stack.contains("msg3")); + assertTrue(stack.contains("msg4")); + assertTrue(stack.contains("msg5")); + } + + @Test + void testRemoveAll() { + final MutableThreadContextStack stack = createStack(); + + stack.removeAll(Arrays.asList("msg1", "msg3")); + assertEquals(1, stack.size()); + assertFalse(stack.contains("msg1")); + assertTrue(stack.contains("msg2")); + assertFalse(stack.contains("msg3")); + } + + @Test + void testRetainAll() { + final MutableThreadContextStack stack = createStack(); + + stack.retainAll(Arrays.asList("msg1", "msg3")); + assertEquals(2, stack.size()); + assertTrue(stack.contains("msg1")); + assertFalse(stack.contains("msg2")); + assertTrue(stack.contains("msg3")); + } + + @Test + void testToStringShowsListContents() { + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); + assertEquals("[]", stack.toString()); + + stack.push("msg1"); + stack.add("msg2"); + stack.push("msg3"); + assertEquals("[msg1, msg2, msg3]", stack.toString()); + + stack.retainAll(Arrays.asList("msg1", "msg3")); + assertEquals("[msg1, msg3]", stack.toString()); + } + + @Test + void testIsFrozenIsFalseByDefault() { + assertFalse(new MutableThreadContextStack().isFrozen()); + assertFalse(createStack().isFrozen()); + } + + @Test + void testIsFrozenIsTrueAfterCallToFreeze() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + assertFalse(stack.isFrozen()); + stack.freeze(); + assertTrue(stack.isFrozen()); + } + + @Test + void testAddAllOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.addAll(Arrays.asList("a", "b", "c"))); + } + + @Test + void testAddOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.add("a")); + } + + @Test + void testClearOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, stack::clear); + } + + @Test + void testPopOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, stack::pop); + } + + @Test + void testPushOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.push("a")); + } + + @Test + void testRemoveOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.remove("a")); + } + + @Test + void testRemoveAllOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.removeAll(Arrays.asList("a", "b"))); + } + + @Test + void testRetainAllOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + assertThrows(UnsupportedOperationException.class, () -> stack.retainAll(Arrays.asList("a", "b"))); + } + + @Test + void testTrimOnFrozenStackThrowsException() { + final MutableThreadContextStack stack = new MutableThreadContextStack(); + stack.freeze(); + 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..6ca21afefad --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.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.status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.junit.jupiter.api.Test; + +public class StatusConsoleListenerTest { + + public static final MessageFactory MESSAGE_FACTORY = ParameterizedNoReferenceMessageFactory.INSTANCE; + + @Test + void StatusData_getFormattedStatus_should_be_used() { + + // Create the listener. + final PrintStream stream = mock(PrintStream.class); + final StatusConsoleListener listener = new StatusConsoleListener(Level.ALL, stream); + + // Log a message. + final Message message = mock(Message.class); + final StatusData statusData = spy(new StatusData(null, Level.TRACE, message, null, null)); + listener.log(statusData); + + // Verify the call. + verify(statusData).getFormattedStatus(); + } + + @Test + void 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); + + // 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 + + // Collect the output. + printStream.flush(); + final String output = outputStream.toString(encoding); + + // Verify the output. + assertThat(output).contains(expectedThrowable.getMessage()).contains(expectedMessage.getFormattedMessage()); + } + + @Test + void non_system_streams_should_be_closed() { + final PrintStream stream = mock(PrintStream.class); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, stream); + listener.close(); + verify(stream).close(); + } + + @Test + void close_should_reset_to_initials() { + + // Create the listener + final PrintStream initialStream = mock(PrintStream.class); + final Level initialLevel = Level.TRACE; + final StatusConsoleListener listener = new StatusConsoleListener(initialLevel, initialStream); + + // Verify the initial state + assertThat(listener.getStatusLevel()).isEqualTo(initialLevel); + assertThat(listener).hasFieldOrPropertyWithValue("stream", initialStream); + + // Update the state + final PrintStream newStream = mock(PrintStream.class); + listener.setStream(newStream); + final Level newLevel = Level.DEBUG; + listener.setLevel(newLevel); + + // Verify the update + verify(initialStream).close(); + assertThat(listener.getStatusLevel()).isEqualTo(newLevel); + assertThat(listener).hasFieldOrPropertyWithValue("stream", newStream); + + // Close the listener + listener.close(); + + // Verify the reset + verify(newStream).close(); + assertThat(listener.getStatusLevel()).isEqualTo(initialLevel); + assertThat(listener).hasFieldOrPropertyWithValue("stream", initialStream); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusDataTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusDataTest.java new file mode 100644 index 00000000000..68a3299b060 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusDataTest.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.status; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Constants; +import org.junit.jupiter.api.Test; + +public class StatusDataTest { + @Test + void test_getFormattedData_does_not_throw() { + // ensure that getFormattedData() does not throw an ArrayIndexOutOfBoundsException when given + // a message with a 0-length (non-null) parameters array + + Message message = new Message() { + @Override + public String getFormattedMessage() { + return "formatted"; + } + + @Override + public Object[] getParameters() { + return Constants.EMPTY_OBJECT_ARRAY; + } + + @Override + public Throwable getThrowable() { + return null; + } + }; + + StatusData statusData = new StatusData(null, Level.ERROR, message, null, null); + assertThat(statusData.getFormattedStatus()).contains("formatted"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerBufferCapacityTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerBufferCapacityTest.java new file mode 100644 index 00000000000..0dc0b5c65d3 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerBufferCapacityTest.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.status; + +import static org.apache.logging.log4j.status.StatusLogger.DEFAULT_FALLBACK_LISTENER_BUFFER_CAPACITY; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; +import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.SystemStubs; + +class StatusLoggerBufferCapacityTest { + + @Test + void valid_buffer_capacity_should_be_effective() { + + // Create a `StatusLogger` configuration + final Properties statusLoggerConfigProperties = new Properties(); + final int bufferCapacity = 10; + assertThat(bufferCapacity).isNotEqualTo(DEFAULT_FALLBACK_LISTENER_BUFFER_CAPACITY); + statusLoggerConfigProperties.put(StatusLogger.MAX_STATUS_ENTRIES, "" + bufferCapacity); + final StatusLogger.Config statusLoggerConfig = new StatusLogger.Config(statusLoggerConfigProperties); + + // Verify the buffer capacity + assertThat(statusLoggerConfig.bufferCapacity).isEqualTo(bufferCapacity); + } + + @Test + void invalid_buffer_capacity_should_cause_fallback_to_defaults() throws Exception { + + // Create a `StatusLogger` configuration using an invalid buffer capacity + final Properties statusLoggerConfigProperties = new Properties(); + final int invalidBufferCapacity = -10; + statusLoggerConfigProperties.put(StatusLogger.MAX_STATUS_ENTRIES, "" + invalidBufferCapacity); + final StatusLogger.Config[] statusLoggerConfigRef = {null}; + final String stderr = SystemStubs.tapSystemErr( + () -> statusLoggerConfigRef[0] = new StatusLogger.Config(statusLoggerConfigProperties)); + final StatusLogger.Config statusLoggerConfig = statusLoggerConfigRef[0]; + + // Verify the stderr dump + assertThat(stderr).contains("Failed reading the buffer capacity"); + + // Verify the buffer capacity + assertThat(statusLoggerConfig.bufferCapacity).isEqualTo(DEFAULT_FALLBACK_LISTENER_BUFFER_CAPACITY); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerDateTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerDateTest.java new file mode 100644 index 00000000000..0978587692a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerDateTest.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.status; + +import static org.assertj.core.api.Assertions.assertThat; + +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Properties; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import uk.org.webcompere.systemstubs.SystemStubs; + +class StatusLoggerDateTest { + + @ParameterizedTest + @CsvSource({"yyyy-MM-dd", "HH:mm:ss", "HH:mm:ss.SSS"}) + void common_date_patterns_should_work(final String instantPattern) { + + // Create a `StatusLogger` configuration + final Properties statusLoggerConfigProperties = new Properties(); + statusLoggerConfigProperties.put(StatusLogger.STATUS_DATE_FORMAT, instantPattern); + final ZoneId zoneId = ZoneId.of("UTC"); + statusLoggerConfigProperties.put(StatusLogger.STATUS_DATE_FORMAT_ZONE, zoneId.toString()); + final StatusLogger.Config statusLoggerConfig = new StatusLogger.Config(statusLoggerConfigProperties); + + // Verify the formatter + final DateTimeFormatter formatter = + DateTimeFormatter.ofPattern(instantPattern).withZone(zoneId); + verifyFormatter(statusLoggerConfig.instantFormatter, formatter); + } + + @Test + void invalid_date_format_should_cause_fallback_to_defaults() throws Exception { + final String invalidFormat = "l"; + verifyInvalidDateFormatAndZone(invalidFormat, "UTC", "failed reading the instant format", null); + } + + @Test + void invalid_date_format_zone_should_cause_fallback_to_defaults() throws Exception { + final String invalidZone = "XXX"; + final String format = "yyyy"; + verifyInvalidDateFormatAndZone( + format, + invalidZone, + "Failed reading the instant formatting zone ID", + DateTimeFormatter.ofPattern(format).withZone(ZoneId.systemDefault())); + } + + private static void verifyInvalidDateFormatAndZone( + final String format, + final String zone, + final String stderrMessage, + @Nullable final DateTimeFormatter formatter) + throws Exception { + + // Create a `StatusLogger` configuration using invalid input + final Properties statusLoggerConfigProperties = new Properties(); + statusLoggerConfigProperties.put(StatusLogger.STATUS_DATE_FORMAT, format); + statusLoggerConfigProperties.put(StatusLogger.STATUS_DATE_FORMAT_ZONE, zone); + final StatusLogger.Config[] statusLoggerConfigRef = {null}; + final String stderr = SystemStubs.tapSystemErr( + () -> statusLoggerConfigRef[0] = new StatusLogger.Config(statusLoggerConfigProperties)); + final StatusLogger.Config statusLoggerConfig = statusLoggerConfigRef[0]; + + // Verify the stderr dump + assertThat(stderr).contains(stderrMessage); + + // Verify the formatter + verifyFormatter(statusLoggerConfig.instantFormatter, formatter); + } + + /** + * {@link DateTimeFormatter} doesn't have an {@link Object#equals(Object)} implementation, hence this manual behavioral comparison. + * + * @param actual the actual formatter + * @param expected the expected formatter + */ + private static void verifyFormatter(@Nullable DateTimeFormatter actual, @Nullable DateTimeFormatter expected) { + if (expected == null) { + assertThat(actual).isNull(); + } else { + assertThat(actual).isNotNull(); + final Instant instant = Instant.now(); + assertThat(actual.format(instant)).isEqualTo(expected.format(instant)); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerFailingListenerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerFailingListenerTest.java new file mode 100644 index 00000000000..ede9cb3af9d --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerFailingListenerTest.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.status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.logging.log4j.Level; +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.junit.jupiter.api.parallel.ResourceLock; +import uk.org.webcompere.systemstubs.SystemStubs; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +@ExtendWith(SystemStubsExtension.class) +@ResourceLock("log4j2.StatusLogger") +class StatusLoggerFailingListenerTest { + + public static final StatusLogger STATUS_LOGGER = StatusLogger.getLogger(); + + private StatusListener listener; + + @BeforeEach + void createAndRegisterListener() { + listener = mock(StatusListener.class); + STATUS_LOGGER.registerListener(listener); + } + + @AfterEach + void unregisterListener() { + STATUS_LOGGER.removeListener(listener); + } + + @Test + void logging_with_failing_listener_should_not_cause_stack_overflow() throws Exception { + + // Set up a failing listener on `log(StatusData)` + when(listener.getStatusLevel()).thenReturn(Level.ALL); + final Exception listenerFailure = new RuntimeException("test failure " + Math.random()); + doThrow(listenerFailure).when(listener).log(any()); + + // Log something and verify exception dump + final String stderr = SystemStubs.tapSystemErr(() -> STATUS_LOGGER.error("foo")); + final String listenerFailureClassName = listenerFailure.getClass().getCanonicalName(); + assertThat(stderr).contains(listenerFailureClassName + ": " + listenerFailure.getMessage()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.java new file mode 100644 index 00000000000..bcc3dd9bb8f --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerLevelTest.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.status; + +import static org.apache.logging.log4j.status.StatusLogger.DEFAULT_FALLBACK_LISTENER_LEVEL; +import static org.apache.logging.log4j.util.Constants.LOG4J2_DEBUG; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +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; +import org.junit.jupiter.params.provider.ValueSource; +import uk.org.webcompere.systemstubs.SystemStubs; + +class StatusLoggerLevelTest { + + @Test + void effective_level_should_be_the_least_specific_one() { + + // Verify the initial level + final StatusLogger logger = new StatusLogger(); + final Level fallbackListenerLevel = DEFAULT_FALLBACK_LISTENER_LEVEL; + assertThat(logger.getLevel()).isEqualTo(fallbackListenerLevel); + + // Register a less specific listener + final StatusListener listener1 = mock(StatusListener.class); + Level listener1Level = Level.WARN; + when(listener1.getStatusLevel()).thenReturn(listener1Level); + logger.registerListener(listener1); + assertThat(listener1Level).isNotEqualTo(fallbackListenerLevel); // Verify that the level is distinct + assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the logger level is changed + + // Register a less specific listener + final StatusListener listener2 = mock(StatusListener.class); + final Level listener2Level = Level.INFO; + when(listener2.getStatusLevel()).thenReturn(listener2Level); + logger.registerListener(listener2); + assertThat(listener2Level) + .isNotEqualTo(fallbackListenerLevel) + .isNotEqualTo(listener1Level); // Verify that the level is distinct + assertThat(logger.getLevel()).isEqualTo(listener2Level); // Verify that the logger level is changed + + // Register a more specific listener + final StatusListener listener3 = mock(StatusListener.class); + final Level listener3Level = Level.ERROR; + when(listener3.getStatusLevel()).thenReturn(listener3Level); + logger.registerListener(listener3); + assertThat(listener3Level) + .isNotEqualTo(listener1Level) + .isNotEqualTo(listener2Level); // Verify that the level is distinct + assertThat(logger.getLevel()).isEqualTo(listener2Level); // Verify that the logger level is not changed + + // Update a registered listener level + listener1Level = Level.DEBUG; + when(listener1.getStatusLevel()).thenReturn(listener1Level); + assertThat(listener1Level) // Verify that the level is distinct + .isNotEqualTo(fallbackListenerLevel) + .isNotEqualTo(listener2Level) + .isNotEqualTo(listener3Level); + assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the logger level is changed + + // Remove the least specific listener + logger.removeListener(listener2); + assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the level is changed + + // Remove the most specific listener + logger.removeListener(listener3); + assertThat(logger.getLevel()).isEqualTo(listener1Level); // Verify that the level is not changed + + // Remove the last listener + logger.removeListener(listener1); + assertThat(logger.getLevel()).isEqualTo(fallbackListenerLevel); // Verify that the level is changed + } + + @Test + void invalid_level_should_cause_fallback_to_defaults() throws Exception { + + // Create a `StatusLogger` configuration using an invalid level + final Properties statusLoggerConfigProperties = new Properties(); + final String invalidLevelName = "FOO"; + statusLoggerConfigProperties.put(StatusLogger.DEFAULT_STATUS_LISTENER_LEVEL, invalidLevelName); + final StatusLogger.Config[] statusLoggerConfigRef = {null}; + final String stderr = SystemStubs.tapSystemErr( + () -> statusLoggerConfigRef[0] = new StatusLogger.Config(statusLoggerConfigProperties)); + final StatusLogger.Config statusLoggerConfig = statusLoggerConfigRef[0]; + + // Verify the stderr dump + assertThat(stderr).contains("Failed reading the level"); + + // Verify the level + assertThat(statusLoggerConfig.fallbackListenerLevel).isEqualTo(DEFAULT_FALLBACK_LISTENER_LEVEL); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void debug_mode_should_override_log_filtering(final boolean debugEnabled) { + + // Create a logger with debug enabled + final StatusLogger.Config loggerConfig = new StatusLogger.Config(debugEnabled, 0, null); + final Level loggerLevel = Level.ERROR; + final StatusConsoleListener fallbackListener = mock(StatusConsoleListener.class); + when(fallbackListener.getStatusLevel()).thenReturn(loggerLevel); + final StatusLogger logger = new StatusLogger( + StatusLoggerLevelTest.class.getSimpleName(), + ParameterizedNoReferenceMessageFactory.INSTANCE, + loggerConfig, + fallbackListener); + + // Log at all levels + final Level[] levels = Level.values(); + for (final Level level : levels) { + logger.log(level, "test for level `{}`", level); + } + + // Calculate the number of expected messages + final int expectedMessageCount; + if (debugEnabled) { + expectedMessageCount = levels.length; + } else { + expectedMessageCount = (int) Arrays.stream(levels) + .filter(loggerLevel::isLessSpecificThan) + .count(); + } + + // Verify the fallback listener invocation + assertThat(expectedMessageCount).isGreaterThan(0); + verify(fallbackListener, times(expectedMessageCount)).log(any()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void debug_mode_should_override_listener_filtering(final boolean debugEnabled) { + + // Create a logger with debug enabled + final StatusLogger.Config loggerConfig = new StatusLogger.Config(debugEnabled, 0, null); + final StatusLogger logger = new StatusLogger( + StatusLoggerLevelTest.class.getSimpleName(), + ParameterizedNoReferenceMessageFactory.INSTANCE, + loggerConfig, + new StatusConsoleListener(Level.ERROR)); + + // Register a listener + final Level listenerLevel = Level.INFO; + final StatusListener listener = mock(StatusListener.class); + when(listener.getStatusLevel()).thenReturn(listenerLevel); + logger.registerListener(listener); + + // Log at all levels + final Level[] levels = Level.values(); + for (final Level level : levels) { + logger.log(level, "test for level `{}`", level); + } + + // Calculate the number of expected messages + final int expectedMessageCount; + if (debugEnabled) { + expectedMessageCount = levels.length; + } else { + expectedMessageCount = (int) Arrays.stream(levels) + .filter(listenerLevel::isLessSpecificThan) + .count(); + } + + // Verify the listener invocation + assertThat(expectedMessageCount).isGreaterThan(0); + verify(listener, times(expectedMessageCount)).log(any()); + } + + @ParameterizedTest + @MethodSource("debugPropertyTestCases") + void debug_property_should_be_read_correctly(final String propertyValue, final boolean debugEnabled) { + final Properties configProperties = new Properties(); + if (propertyValue != null) { + configProperties.put(LOG4J2_DEBUG, propertyValue); + } + final StatusLogger.Config config = new StatusLogger.Config(configProperties); + assertThat(config.debugEnabled).isEqualTo(debugEnabled); + } + + static List debugPropertyTestCases() { + List testCases = new ArrayList<>(); + Arrays.asList("t", "true", "True", "TRUE", "f", "fals", "falsx") + .forEach(propertyValue -> testCases.add(Arguments.of(propertyValue, true))); + Arrays.asList("false", "FALSE", "faLSe") + .forEach(propertyValue -> testCases.add(Arguments.of(propertyValue, false))); + return testCases; + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerPropertiesUtilDoubleTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerPropertiesUtilDoubleTest.java new file mode 100644 index 00000000000..94ad4903d28 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerPropertiesUtilDoubleTest.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.status; + +import static java.util.Collections.singletonMap; +import static org.apache.logging.log4j.status.StatusLogger.PropertiesUtilsDouble.readAllAvailableProperties; +import static org.apache.logging.log4j.status.StatusLogger.PropertiesUtilsDouble.readProperty; +import static org.assertj.core.api.Assertions.assertThat; +import static uk.org.webcompere.systemstubs.SystemStubs.restoreSystemProperties; +import static uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariable; + +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class StatusLoggerPropertiesUtilDoubleTest { + + private static final String[] MATCHING_PROPERTY_NAMES = new String[] { + // System properties for version range `[, 2.10)` + "log4j2.StatusLogger.DateFormat", + // System properties for version range `[2.10, 3)` + "log4j2.statusLoggerDateFormat", + // System properties for version range `[3,)` + "log4j2.StatusLogger.dateFormat", + // Environment variables + "LOG4J_STATUS_LOGGER_DATE_FORMAT" + }; + + private static final String[] NOT_MATCHING_PROPERTY_NAMES = + new String[] {"log4j2.StatusLogger$DateFormat", "log4j2.StàtusLögger.DateFormat"}; + + private static final class TestCase { + + private final boolean matching; + + private final String propertyName; + + private final String userProvidedPropertyName; + + private TestCase(final boolean matching, final String propertyName, final String userProvidedPropertyName) { + this.matching = matching; + this.propertyName = propertyName; + this.userProvidedPropertyName = userProvidedPropertyName; + } + + @Override + public String toString() { + return String.format("`%s` %s `%s`", propertyName, matching ? "==" : "!=", userProvidedPropertyName); + } + } + + static Stream testCases() { + return Stream.concat( + testCases(true, MATCHING_PROPERTY_NAMES, MATCHING_PROPERTY_NAMES), + testCases(false, MATCHING_PROPERTY_NAMES, NOT_MATCHING_PROPERTY_NAMES)); + } + + private static Stream testCases( + final boolean matching, final String[] propertyNames, final String[] userProvidedPropertyNames) { + return Arrays.stream(propertyNames).flatMap(propertyName -> Arrays.stream(userProvidedPropertyNames) + .map(userProvidedPropertyName -> new TestCase(matching, propertyName, userProvidedPropertyName))); + } + + @ParameterizedTest + @MethodSource("testCases") + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE) + void system_properties_should_work(final TestCase testCase) throws Exception { + restoreSystemProperties(() -> { + final String expectedValue = "foo"; + System.setProperty(testCase.propertyName, expectedValue); + verifyProperty(testCase, expectedValue); + }); + } + + @ParameterizedTest + @MethodSource("testCases") + @ResourceLock(value = Resources.GLOBAL, mode = ResourceAccessMode.READ_WRITE) + void environment_variables_should_work(final TestCase testCase) throws Exception { + final String expectedValue = "bar"; + withEnvironmentVariable(testCase.propertyName, expectedValue).execute(() -> { + verifyProperty(testCase, expectedValue); + }); + } + + private static void verifyProperty(final TestCase testCase, final String expectedValue) { + final Map normalizedProperties = readAllAvailableProperties(); + final String actualValue = readProperty(normalizedProperties, testCase.userProvidedPropertyName); + if (testCase.matching) { + assertThat(actualValue).describedAs("" + testCase).isEqualTo(expectedValue); + } else { + assertThat(actualValue).describedAs("" + testCase).isNull(); + } + } + + @Test + void properties_file_in_class_path_should_be_read() { + final String propertiesFileName = StatusLoggerPropertiesUtilDoubleTest.class.getSimpleName() + ".properties"; + final Properties actualProperties = StatusLogger.PropertiesUtilsDouble.readPropertiesFile(propertiesFileName); + assertThat(actualProperties).containsExactlyEntriesOf(singletonMap("foo", "bar")); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerResetTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerResetTest.java new file mode 100644 index 00000000000..07fe4ca2955 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerResetTest.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.status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.PrintStream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.junit.jupiter.api.Test; + +class StatusLoggerResetTest { + + @Test + void test_reset() throws IOException { + + // Create the fallback listener + final PrintStream fallbackListenerInitialStream = mock(PrintStream.class); + final Level fallbackListenerInitialLevel = Level.INFO; + final StatusConsoleListener fallbackListener = + new StatusConsoleListener(fallbackListenerInitialLevel, fallbackListenerInitialStream); + + // Create the `StatusLogger` + final StatusLogger.Config loggerConfig = new StatusLogger.Config(false, 0, null); + final StatusLogger logger = new StatusLogger( + StatusLoggerResetTest.class.getSimpleName(), + ParameterizedNoReferenceMessageFactory.INSTANCE, + loggerConfig, + fallbackListener); + + // Create and register a listener + final Level listenerLevel = Level.DEBUG; + assertThat(listenerLevel.isLessSpecificThan(fallbackListenerInitialLevel)) + .isTrue(); + final StatusListener listener = mock(StatusListener.class); + when(listener.getStatusLevel()).thenReturn(listenerLevel); + logger.registerListener(listener); + + // Verify the `StatusLogger` state + assertThat(logger.getLevel()).isEqualTo(listenerLevel); + assertThat(logger.getListeners()).containsExactly(listener); + + // Update the fallback listener + final PrintStream fallbackListenerNewStream = mock(PrintStream.class); + fallbackListener.setStream(fallbackListenerNewStream); + verify(fallbackListenerInitialStream).close(); + final Level fallbackListenerNewLevel = Level.TRACE; + assertThat(fallbackListenerNewLevel.isLessSpecificThan(listenerLevel)).isTrue(); + fallbackListener.setLevel(fallbackListenerNewLevel); + + // Verify the `StatusLogger` state + assertThat(logger.getLevel()).isEqualTo(fallbackListenerNewLevel); + + // Reset the `StatusLogger` and verify the state + logger.reset(); + verify(listener).close(); + verify(fallbackListenerNewStream).close(); + assertThat(logger.getLevel()).isEqualTo(fallbackListenerInitialLevel); + assertThat(logger.getListeners()).isEmpty(); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerTest.java new file mode 100644 index 00000000000..85921e8071a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StatusLoggerTest { + private final PrintStream origOut = System.out; + private final PrintStream origErr = System.err; + private ByteArrayOutputStream outBuf; + private ByteArrayOutputStream errBuf; + + @BeforeEach + void setupStreams() { + outBuf = new ByteArrayOutputStream(); + errBuf = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outBuf)); + System.setErr(new PrintStream(errBuf)); + } + + @AfterEach + void resetStreams() { + System.setOut(origOut); + System.setErr(origErr); + } + + @Test + void status_logger_writes_to_stderr_by_default() { + StatusLogger statusLogger = new StatusLogger(); + statusLogger.error("Test message"); + + assertEquals("", outBuf.toString()); + assertThat(errBuf.toString()).contains("Test message"); + } +} 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..bdc854d0e31 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java @@ -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. + */ +package org.apache.logging.log4j.test; + +public interface BetterService extends Service {} 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..1b05ec8d4b6 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java @@ -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. + */ +package org.apache.logging.log4j.test; + +public interface Service {} 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..453bffaf986 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java @@ -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. + */ +package org.apache.logging.log4j.test; + +public class Service1 implements Service {} 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..9c3d5be51b7 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java @@ -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. + */ +package org.apache.logging.log4j.test; + +public class Service2 implements BetterService {} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TempLoggingDirectoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TempLoggingDirectoryTest.java new file mode 100644 index 00000000000..96ebbd50d5d --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TempLoggingDirectoryTest.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 static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; +import org.apache.logging.log4j.test.TestProperties; +import org.junit.jupiter.api.Test; + +@UsingTestProperties +class TempLoggingDirectoryTest { + + private static final Pattern PER_CLASS_PATH = Pattern.compile("TempLoggingDirectoryTest_[0-9a-f]{8,}_\\d+"); + private static final Path PER_TEST_PATH = Paths.get("testInjectedFields"); + + @TempLoggingDir + private static Path staticLoggingPath; + + @TempLoggingDir + private Path instanceLoggingPath; + + @Test + void testInjectedFields(final @TempLoggingDir Path parameterLoggingPath, final TestProperties props) { + assertThat(staticLoggingPath).exists(); + assertThat(staticLoggingPath.getFileName().toString()).matches(PER_CLASS_PATH); + assertThat(instanceLoggingPath).exists().startsWith(staticLoggingPath).endsWith(PER_TEST_PATH); + assertThat(parameterLoggingPath).isEqualTo(instanceLoggingPath); + assertThat(props.getProperty(TestProperties.LOGGING_PATH)).isEqualTo(instanceLoggingPath.toString()); + } +} 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..4e659e175ff --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TestPropertySourceTest.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.test.junit; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; + +@UsingTestProperties +class TestPropertySourceTest { + + private static TestProperties staticProperties; + private TestProperties instanceProperties; + + @Test + void testInjectedFields() { + assertThat(staticProperties).isNotNull(); + assertThat(instanceProperties).isNotNull(); + + // Test that per-class properties are overridden by per-test properties + final PropertiesUtil 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 + void testInjectedParameter(final TestProperties paramProperties) { + assertThat(paramProperties).isEqualTo(instanceProperties); + } + + @Test + @SetTestProperty(key = "log4j2.testSetTestProperty", value = "true") + void testSetTestProperty() { + final PropertiesUtil env = PropertiesUtil.getProperties(); + assertThat(env.getBooleanProperty("log4j2.testSetTestProperty")).isTrue(); + } +} 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..cca365710fe --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class CharsTest { + @ParameterizedTest + @ValueSource(ints = {-1, 16, 400, -1, 16, 400}) + void invalidDigitReturnsNullCharacter(final int invalidDigit) { + assertAll( + () -> assertEquals('\0', Chars.getUpperCaseHex(invalidDigit)), + () -> assertEquals('\0', Chars.getLowerCaseHex(invalidDigit))); + } + + @Test + 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-test/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java new file mode 100644 index 00000000000..a84eb3c1cb9 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.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.util; + +import java.nio.charset.Charset; + +public class CharsetForNameMain { + + /** + * Checks that the given Charset names can be loaded. + */ + public static void main(final String[] args) { + for (final String value : args) { + final String charsetName = value.trim(); + if (Charset.isSupported(charsetName)) { + final Charset cs = Charset.forName(charsetName); + System.out.println(String.format("%s -> %s aliases: %s", charsetName, cs.name(), cs.aliases())); + } else { + System.err.println("Not supported:" + charsetName); + } + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassLocator.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassLocator.java new file mode 100644 index 00000000000..d7daa7f55c2 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassLocator.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.util; + +/** + * Created by rgoers on 3/15/17. + */ +public class ClassLocator { + + public Class locateClass() { + return StackLocatorUtil.getCallerClass(ClassLocator.class); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java new file mode 100644 index 00000000000..f50001330eb --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.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.util; + +/** + * + */ +public class ClassNameLocator { + + public StackTraceElement locateClass() { + return StackLocatorUtil.calcLocation(ClassNameLocator.class.getName()); + } +} 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..8735b0d1d00 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ConstantsTest { + + @Test + 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-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java new file mode 100644 index 00000000000..2261617d850 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.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.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * Deserializes a specified file. + * + * @see SortedArrayStringMapTest#testDeserializationOfUnknownClass() + */ +public class DeserializerHelper { + public static void main(final String... args) throws Exception { + final File file = new File(args[0]); + final Collection allowedExtraClasses = + args.length > 1 ? Arrays.asList(args).subList(1, args.length) : Collections.emptyList(); + ObjectInputStream in = null; + try { + in = new FilteredObjectInputStream(new FileInputStream(file), allowedExtraClasses); + final Object result = in.readObject(); + System.out.println(result); + } catch (final Throwable t) { + System.err.println("Could not deserialize."); + throw t; // cause non-zero exit code + } finally { + try { + in.close(); + } catch (final Throwable t) { + System.err.println("Error while closing: " + t); + } + } + } +} 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..b90dc9a8725 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceSecurityManagerIT.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.util; + +import java.security.Permission; +import org.apache.logging.log4j.test.junit.SecurityManagerTestRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Tests https://issues.apache.org/jira/browse/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") +public class EnvironmentPropertySourceSecurityManagerIT { + + @Rule + public final SecurityManagerTestRule rule = new SecurityManagerTestRule(new TestSecurityManager()); + + /** + * 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() { + PropertiesUtil.getProperties(); + } +} 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..83f11e852b1 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; + +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; + +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") + void testNormalFormFollowsEnvironmentVariableConventions( + final CharSequence expected, final 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..1632afaae9b --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java @@ -0,0 +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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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 org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +/** + * Tests the LambdaUtil class. + */ +class LambdaUtilTest { + + @Test + void testGetSupplierResultOfSupplier() { + final String expected = "result"; + final Object actual = LambdaUtil.get((Supplier) () -> expected); + assertSame(expected, actual); + } + + @Test + void testGetMessageSupplierResultOfSupplier() { + final Message expected = new SimpleMessage("hi"); + final Message actual = LambdaUtil.get(() -> expected); + assertSame(expected, actual); + } + + @Test + void testGetSupplierReturnsNullIfSupplierNull() { + final Object actual = LambdaUtil.get((Supplier) null); + assertNull(actual); + } + + @Test + void testGetMessageSupplierReturnsNullIfSupplierNull() { + final Object actual = LambdaUtil.get(null); + assertNull(actual); + } + + @Test + void testGetSupplierExceptionIfSupplierThrowsException() { + assertThrows( + RuntimeException.class, + () -> LambdaUtil.get((Supplier) () -> { + throw new RuntimeException(); + })); + } + + @Test + void testGetMessageSupplierExceptionIfSupplierThrowsException() { + assertThrows( + RuntimeException.class, + () -> LambdaUtil.get(() -> { + throw new RuntimeException(); + })); + } + + @Test + 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 + void testGetAllReturnsNullArrayIfSupplierArrayNull() { + final Object[] actual = LambdaUtil.getAll((Supplier[]) null); + assertNull(actual); + } + + @Test + 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 + 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 83% 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..40afae1d3ce 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 @@ -1,45 +1,33 @@ /* * 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.logging.log4j.util; -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -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; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; - public LegacyPropertiesCompatibilityTest(final CharSequence newName, final CharSequence oldName) { - this.newName = newName; - this.oldName = oldName; - } +class LegacyPropertiesCompatibilityTest { - @Parameterized.Parameters(name = "New: {0}; Old: {1}") public static Object[][] data() { - return new Object[][]{ + 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 +82,11 @@ public static Object[][] data() { }; } - @Test - public void compareNewWithOldName() throws Exception { + @ParameterizedTest + @MethodSource("data") + 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/LoaderUtilSecurityManagerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilSecurityManagerTest.java new file mode 100644 index 00000000000..beb7cc345b6 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilSecurityManagerTest.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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.security.Permission; +import org.apache.logging.log4j.test.junit.SecurityManagerTestRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +@ResourceLock("java.lang.SecurityManager") +public class LoaderUtilSecurityManagerTest { + @Rule + public final SecurityManagerTestRule rule = new SecurityManagerTestRule(new TestSecurityManager()); + + private static class TestSecurityManager extends SecurityManager { + @Override + public void checkPermission(final Permission perm) { + if (perm.equals(LoaderUtil.GET_CLASS_LOADER)) { + throw new SecurityException("disabled"); + } + } + } + + @Test + public void canGetClassLoaderThroughPrivileges() { + assertFalse(LoaderUtil.GET_CLASS_LOADER_DISABLED); + assertDoesNotThrow(() -> LoaderUtil.getClassLoader(LoaderUtilSecurityManagerTest.class, String.class)); + } +} 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..20d03654bcd --- /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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.Charset; +import java.util.Enumeration; +import java.util.ResourceBundle; +import org.junit.jupiter.api.Test; + +class Log4jCharsetsPropertiesTest { + + /** + * Tests that we can load all mappings. + */ + @Test + void testLoadAll() { + final ResourceBundle resourceBundle = PropertiesUtil.getCharsetsResourceBundle(); + final Enumeration keys = resourceBundle.getKeys(); + while (keys.hasMoreElements()) { + final String key = keys.nextElement(); + assertFalse( + Charset.isSupported(key), + String.format("The Charset %s is available and should not be mapped", key)); + final 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-test/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java new file mode 100644 index 00000000000..0460d3a47bd --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/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.util; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +class ProcessIdUtilTest { + + @Test + void processIdTest() { + final String processId = ProcessIdUtil.getProcessId(); + assertNotEquals(ProcessIdUtil.DEFAULT_PROCESSID, processId, "ProcessId is default"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java new file mode 100644 index 00000000000..bf82ed2b3a2 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class PropertiesPropertySourceTest { + + private final PropertySource source = new PropertiesPropertySource(new Properties()); + + 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()} + }; + } + + @ParameterizedTest + @MethodSource("data") + void testNormalFormFollowsCamelCaseConventions(final String expected, final List tokens) { + assertEquals(expected, source.getNormalForm(tokens)); + } +} 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..3e07998606d --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilOrderTest.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 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.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; + +@ExtendWith(SystemStubsExtension.class) +@ResourceLock(value = Resources.SYSTEM_PROPERTIES) +class PropertiesUtilOrderTest { + + private 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(final Iterable tokens) { + final CharSequence camelCase = PropertySource.Util.joinAsCamelCase(tokens); + return camelCase.length() > 0 ? "log4j2." + camelCase : null; + } + + @Override + public String getProperty(final String key) { + return props.getProperty(key); + } + + @Override + public boolean containsProperty(final String key) { + return getProperty(key) != null; + } + } + + private final Properties properties = new Properties(); + + @BeforeEach + void setUp() throws Exception { + try (final InputStream is = ClassLoader.getSystemResourceAsStream("PropertiesUtilOrderTest.properties")) { + properties.load(is); + } + } + + @Test + 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 + void testOrderOfNormalizedProperties(final EnvironmentVariables env, final 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")); + assertNull(util.getStringProperty("Log4jNormalizedProperty")); + assertFalse(util.hasProperty("log4j2.normalizedProperty")); + assertNull(util.getStringProperty("log4j2.normalizedProperty")); + + properties.setProperty("log4j2.normalizedProperty", "props.normalized"); + 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"); + 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"); + assertTrue(util.hasProperty("Log4jNormalizedProperty")); + assertEquals("sysProps", util.getStringProperty("Log4jNormalizedProperty")); + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("sysProps", util.getStringProperty("log4j2.normalizedProperty")); + } + + @Test + void testLegacySystemPropertyHasHigherPriorityThanEnv( + final EnvironmentVariables env, final SystemProperties sysProps) { + env.set("LOG4J_CONFIGURATION_FILE", "env"); + final PropertiesUtil util = new PropertiesUtil(properties); + + assertTrue(util.hasProperty("log4j.configurationFile")); + assertEquals("env", util.getStringProperty("log4j.configurationFile")); + + sysProps.set("log4j.configurationFile", "legacy"); + assertTrue(util.hasProperty("log4j.configurationFile")); + assertEquals("legacy", util.getStringProperty("log4j.configurationFile")); + + sysProps.set("log4j2.configurationFile", "new"); + assertTrue(util.hasProperty("log4j.configurationFile")); + assertEquals("new", util.getStringProperty("log4j.configurationFile")); + } + + @Test + void testHighPriorityNonEnumerableSource(final 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, using a {@link PropertySource}, which returns + * {@code null} in almost every call. + */ + @Test + void testNullChecks(final SystemProperties sysProps) { + sysProps.set("log4j2.someProperty", "sysProps"); + sysProps.set("Log4jLegacyProperty", "sysProps"); + final PropertiesUtil util = new PropertiesUtil(() -> Integer.MIN_VALUE); + + assertTrue(util.hasProperty("Log4jSomeProperty")); + assertEquals("sysProps", util.getStringProperty("log4jSomeProperty")); + assertTrue(util.hasProperty("Log4jLegacyProperty")); + assertEquals("sysProps", util.getStringProperty("Log4jLegacyProperty")); + 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..5f85d13256e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java @@ -0,0 +1,359 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MICROS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.Issue; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +class PropertiesUtilTest { + + private final Properties properties = new Properties(); + + @BeforeEach + void setUp() throws Exception { + properties.load(ClassLoader.getSystemResourceAsStream("PropertiesUtilTest.properties")); + } + + @Test + void testExtractSubset() { + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "a")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "b.")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "c.1")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "dd")); + // One invalid entry remains + assertEquals(1, properties.size()); + } + + @Test + 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 + 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")); + } + + static Stream should_properly_parse_duration() { + return Stream.of( + Arguments.of(Duration.of(1, NANOS), "1 ns"), + Arguments.of(Duration.of(1, NANOS), "1 nano"), + Arguments.of(Duration.of(3, NANOS), "3 nanos"), + Arguments.of(Duration.of(1, NANOS), "1 nanosecond"), + Arguments.of(Duration.of(5, NANOS), "5 nanoseconds"), + Arguments.of(Duration.of(6, MICROS), "6 us"), + Arguments.of(Duration.of(1, MICROS), "1 micro"), + Arguments.of(Duration.of(8, MICROS), "8 micros"), + Arguments.of(Duration.of(1, MICROS), "1 microsecond"), + Arguments.of(Duration.of(10, MICROS), "10 microseconds"), + Arguments.of(Duration.of(11, MILLIS), "11 ms"), + Arguments.of(Duration.of(1, MILLIS), "1 milli"), + Arguments.of(Duration.of(13, MILLIS), "13 millis"), + Arguments.of(Duration.of(1, MILLIS), "1 millisecond"), + Arguments.of(Duration.of(15, MILLIS), "15 milliseconds"), + Arguments.of(Duration.of(16, SECONDS), "16 s"), + Arguments.of(Duration.of(1, SECONDS), "1 second"), + Arguments.of(Duration.of(18, SECONDS), "18 seconds"), + Arguments.of(Duration.of(19, MINUTES), "19 m"), + Arguments.of(Duration.of(1, MINUTES), "1 minute"), + Arguments.of(Duration.of(21, MINUTES), "21 minutes"), + Arguments.of(Duration.of(22, HOURS), "22 h"), + Arguments.of(Duration.of(1, HOURS), "1 hour"), + Arguments.of(Duration.of(24, HOURS), "24 hours"), + Arguments.of(Duration.of(25, DAYS), "25 d"), + Arguments.of(Duration.of(1, DAYS), "1 day"), + Arguments.of(Duration.of(27, DAYS), "27 days"), + Arguments.of(Duration.of(28, MILLIS), "28")); + } + + @ParameterizedTest + @MethodSource + void should_properly_parse_duration(final Duration expected, final CharSequence value) { + assertThat(PropertiesUtil.parseDuration(value)).isEqualTo(expected); + } + + static List should_throw_on_invalid_duration() { + return Arrays.asList( + // more than long + "18446744073709551616 nanos", "1 month", "invalid pattern"); + } + + @ParameterizedTest + @MethodSource + void should_throw_on_invalid_duration(final CharSequence value) { + assertThrows(IllegalArgumentException.class, () -> PropertiesUtil.parseDuration(value)); + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + void testGetMappedProperty_sun_stdout_encoding() { + final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); + final 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) + void testGetMappedProperty_sun_stderr_encoding() { + final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); + final Charset expected = System.console() == null ? Charset.defaultCharset() : StandardCharsets.UTF_8; + assertEquals(expected, pu.getCharsetProperty("sun.err.encoding")); + } + + @Test + @ResourceLock(Resources.SYSTEM_PROPERTIES) + void testNonStringSystemProperties() { + final Object key1 = "1"; + final 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) + void testPublish() { + final Properties props = new Properties(); + new PropertiesUtil(props); + final String value = System.getProperty("Application"); + assertNotNull(value, "System property was not published"); + assertEquals("Log4j", value); + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + @Issue("https://github.com/spring-projects/spring-boot/issues/33450") + @UsingStatusListener + void testErrorPropertySource(ListStatusListener statusListener) { + final String key = "testKey"; + final Properties props = new Properties(); + props.put(key, "test"); + final PropertiesUtil util = new PropertiesUtil(props); + final ErrorPropertySource source = new ErrorPropertySource(); + util.addPropertySource(source); + try { + statusListener.clear(); + assertEquals("test", util.getStringProperty(key)); + assertTrue(source.exceptionThrown); + assertThat(statusListener.findStatusData(Level.WARN)) + .anySatisfy(data -> + assertThat(data.getMessage().getFormattedMessage()).contains("Failed")); + } finally { + util.removePropertySource(source); + } + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + @Issue("https://github.com/apache/logging-log4j2/issues/3252") + @UsingStatusListener + void testRecursivePropertySource(ListStatusListener statusListener) { + final String key = "testKey"; + final Properties props = new Properties(); + props.put(key, "test"); + final PropertiesUtil util = new PropertiesUtil(props); + final PropertySource source = new RecursivePropertySource(util); + util.addPropertySource(source); + try { + // We ignore the recursive source + statusListener.clear(); + assertThat(util.getStringProperty(key)).isEqualTo("test"); + assertThat(statusListener.findStatusData(Level.WARN)) + .anySatisfy(data -> assertThat(data.getMessage().getFormattedMessage()) + .contains("Recursive call", "getProperty")); + + statusListener.clear(); + // To check for existence, the sources are looked up in a random order. + assertThat(util.hasProperty(key)).isTrue(); + // To find a missing key, all the sources must be used. + assertThat(util.hasProperty("noSuchKey")).isFalse(); + assertThat(statusListener.findStatusData(Level.WARN)) + .anySatisfy(data -> assertThat(data.getMessage().getFormattedMessage()) + .contains("Recursive call", "containsProperty")); + // We check that the source is recursive + assertThat(source.getProperty(key)).isEqualTo("test"); + assertThat(source.containsProperty(key)).isTrue(); + } finally { + util.removePropertySource(source); + } + } + + private static final String[][] data = { + {null, "org.apache.logging.log4j.level"}, + {null, "Log4jAnotherProperty"}, + {null, "log4j2.catalinaBase"}, + {"ok", "log4j.configurationFile"}, + {"ok", "Log4jDefaultStatusLevel"}, + {"ok", "org.apache.logging.log4j.newLevel"}, + {"ok", "AsyncLogger.Timeout"}, + {"ok", "AsyncLoggerConfig.RingBufferSize"}, + {"ok", "disableThreadContext"}, + {"ok", "disableThreadContextStack"}, + {"ok", "disableThreadContextMap"}, + {"ok", "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) + void testResolvesOnlyLog4jProperties() { + final PropertiesUtil util = new PropertiesUtil("Jira3413Test.properties"); + for (final String[] pair : data) { + assertThat(util.getStringProperty(pair[1])) + .as("Checking property %s", pair[1]) + .isEqualTo(pair[0]); + } + } + + /** + * LOG4J2-3559: the fix for LOG4J2-3413 returns the value of 'log4j2.' for each + * property not starting with 'log4j'. + */ + @Test + @ReadsSystemProperty + 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)); + } + + @Test + void should_support_multiple_sources_with_same_priority() { + final int priority = 2003; + final String key1 = "propertySource1"; + final Properties props1 = new Properties(); + props1.put(key1, "props1"); + final String key2 = "propertySource2"; + final Properties props2 = new Properties(); + props2.put(key2, "props2"); + final PropertiesUtil util = new PropertiesUtil(new PropertiesPropertySource(props1, priority)); + util.addPropertySource(new PropertiesPropertySource(props2, priority)); + assertThat(util.getStringProperty(key1)).isEqualTo("props1"); + assertThat(util.getStringProperty(key2)).isEqualTo("props2"); + } + + private static class ErrorPropertySource implements PropertySource { + public boolean exceptionThrown = false; + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public String getProperty(final String key) { + exceptionThrown = true; + throw new IllegalStateException("Test"); + } + + @Override + public boolean containsProperty(final String key) { + exceptionThrown = true; + throw new IllegalStateException("Test"); + } + } + + private static class RecursivePropertySource implements PropertySource { + + private final PropertiesUtil propertiesUtil; + + private RecursivePropertySource(PropertiesUtil propertiesUtil) { + this.propertiesUtil = propertiesUtil; + } + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public String getProperty(String key) { + return propertiesUtil.getStringProperty(key); + } + + @Override + public boolean containsProperty(String key) { + return propertiesUtil.hasProperty(key); + } + } +} 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..7d40e110e69 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertyFilePropertySourceSecurityManagerIT.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.util; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.FilePermission; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Permission; +import java.util.PropertyPermission; +import org.apache.logging.log4j.test.junit.SecurityManagerTestRule; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Test related to https://issues.apache.org/jira/browse/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") +public class PropertyFilePropertySourceSecurityManagerIT { + + @BeforeClass + public static void beforeClass() { + assertTrue(TEST_FIXTURE_PATH, Files.exists(Paths.get(TEST_FIXTURE_PATH))); + } + + @Rule + public final SecurityManagerTestRule rule = new SecurityManagerTestRule(new TestSecurityManager()); + + 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() { + final PropertiesUtil propertiesUtil = new PropertiesUtil(TEST_FIXTURE_PATH); + assertNull(propertiesUtil.getStringProperty("a.1")); + } +} 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..ebc659a470a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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; + +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") + void testJoinAsCamelCase(final CharSequence expected, final List tokens) { + assertEquals(expected, PropertySource.Util.joinAsCamelCase(tokens)); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java new file mode 100644 index 00000000000..ac19573f547 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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; + +class PropertySourceTokenizerTest { + + public static Object[][] data() { + return new Object[][] { + {"log4j.simple", Collections.singletonList("simple")}, + {"log4j_simple", Collections.singletonList("simple")}, + {"log4j-simple", Collections.singletonList("simple")}, + {"log4j/simple", Collections.singletonList("simple")}, + {"log4j2.simple", Collections.singletonList("simple")}, + {"Log4jSimple", Collections.singletonList("simple")}, + {"LOG4J_simple", Collections.singletonList("simple")}, + {"org.apache.logging.log4j.simple", Collections.singletonList("simple")}, + {"log4j.simpleProperty", Arrays.asList("simple", "property")}, + {"log4j.simple_property", Arrays.asList("simple", "property")}, + {"LOG4J_simple_property", Arrays.asList("simple", "property")}, + {"LOG4J_SIMPLE_PROPERTY", Arrays.asList("simple", "property")}, + {"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()} + }; + } + + @ParameterizedTest + @MethodSource("data") + void testTokenize(final String value, final List expectedTokens) { + final List tokens = PropertySource.Util.tokenize(value); + assertEquals(expectedTokens, tokens); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java new file mode 100644 index 00000000000..8d67050cc2b --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Properties; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.apache.logging.log4j.TestProvider; +import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.TestLoggerContextFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@Execution(ExecutionMode.CONCURRENT) +class ProviderUtilTest { + + private static final Pattern ERROR_OR_WARNING = Pattern.compile(" (ERROR|WARN) .*", Pattern.DOTALL); + + private static final Provider LOCAL_PROVIDER = new LocalProvider(); + private static final Provider TEST_PROVIDER = new TestProvider(); + + private static final Collection NO_PROVIDERS = Collections.emptyList(); + private static final Collection ONE_PROVIDER = Collections.singleton(TEST_PROVIDER); + private static final Collection TWO_PROVIDERS = Arrays.asList(LOCAL_PROVIDER, TEST_PROVIDER); + + private TestLogger statusLogger; + + @BeforeEach + void setup() { + statusLogger = new TestLogger(); + } + + @Test + void should_have_a_fallback_provider() { + final PropertiesUtil properties = new PropertiesUtil(new Properties()); + assertThat(ProviderUtil.selectProvider(properties, NO_PROVIDERS, statusLogger)) + .as("check selected provider") + .isNotNull(); + // An error for the absence of providers + assertHasErrorOrWarning(statusLogger); + } + + @Test + void should_be_silent_with_a_single_provider() { + final PropertiesUtil properties = new PropertiesUtil(new Properties()); + assertThat(ProviderUtil.selectProvider(properties, ONE_PROVIDER, statusLogger)) + .as("check selected provider") + .isSameAs(TEST_PROVIDER); + assertNoErrorsOrWarnings(statusLogger); + } + + @Test + void should_select_provider_with_highest_priority() { + final PropertiesUtil properties = new PropertiesUtil(new Properties()); + assertThat(ProviderUtil.selectProvider(properties, TWO_PROVIDERS, statusLogger)) + .as("check selected provider") + .isSameAs(TEST_PROVIDER); + // A warning for the presence of multiple providers + assertHasErrorOrWarning(statusLogger); + } + + @Test + void should_recognize_log4j_provider_property() { + final Properties map = new Properties(); + map.setProperty("log4j.provider", LocalProvider.class.getName()); + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties, TWO_PROVIDERS, statusLogger)) + .as("check selected provider") + .isInstanceOf(LocalProvider.class); + assertNoErrorsOrWarnings(statusLogger); + } + + /** + * Can be removed in the future. + */ + @Test + void should_recognize_log4j_factory_property() { + final Properties map = new Properties(); + map.setProperty("log4j2.loggerContextFactory", LocalLoggerContextFactory.class.getName()); + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties, TWO_PROVIDERS, statusLogger) + .getLoggerContextFactory()) + .as("check selected logger context factory") + .isInstanceOf(LocalLoggerContextFactory.class); + // Deprecation warning + assertHasErrorOrWarning(statusLogger); + } + + /** + * Can be removed in the future. + */ + @Test + void log4j_provider_property_has_priority() { + final Properties map = new Properties(); + map.setProperty("log4j.provider", LocalProvider.class.getName()); + map.setProperty("log4j2.loggerContextFactory", TestLoggerContextFactory.class.getName()); + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties, TWO_PROVIDERS, statusLogger)) + .as("check selected provider") + .isInstanceOf(LocalProvider.class); + // Warning + assertHasErrorOrWarning(statusLogger); + } + + static Stream incorrect_configuration_do_not_throw() { + return Stream.of( + Arguments.of("java.lang.String", null), + Arguments.of("non.existent.Provider", null), + // logger context factory without a matching provider + Arguments.of(null, "org.apache.logging.log4j.util.ProviderUtilTest$LocalLoggerContextFactory"), + Arguments.of(null, "java.lang.String"), + Arguments.of(null, "non.existent.LoggerContextFactory")); + } + + @ParameterizedTest + @MethodSource + void incorrect_configuration_do_not_throw(final String provider, final String contextFactory) { + final Properties map = new Properties(); + if (provider != null) { + map.setProperty("log4j.provider", provider); + } + if (contextFactory != null) { + map.setProperty("log4j2.loggerContextFactory", contextFactory); + } + final PropertiesUtil properties = new PropertiesUtil(map); + assertThat(ProviderUtil.selectProvider(properties, ONE_PROVIDER, statusLogger)) + .as("check selected provider") + .isNotNull(); + // Warnings will be present + assertHasErrorOrWarning(statusLogger); + } + + public static class LocalLoggerContextFactory extends TestLoggerContextFactory {} + + /** + * A provider with a smaller priority than {@link org.apache.logging.log4j.TestProvider}. + */ + public static class LocalProvider extends org.apache.logging.log4j.spi.Provider { + public LocalProvider() { + super(0, CURRENT_VERSION, LocalLoggerContextFactory.class); + } + } + + private void assertHasErrorOrWarning(final TestLogger statusLogger) { + assertThat(statusLogger.getEntries()).as("check StatusLogger entries").anySatisfy(entry -> assertThat(entry) + .matches(ERROR_OR_WARNING)); + } + + private void assertNoErrorsOrWarnings(final TestLogger statusLogger) { + assertThat(statusLogger.getEntries()).as("check StatusLogger entries").allSatisfy(entry -> assertThat(entry) + .doesNotMatch(ERROR_OR_WARNING)); + } +} 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..927d6e3e772 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.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.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.BetterService; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.Service; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +class ServiceLoaderUtilTest { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + @Test + void testServiceResolution() { + final List services = new ArrayList<>(); + ServiceLoaderUtil.safeStream( + BetterService.class, + ServiceLoader.load(BetterService.class, getClass().getClassLoader()), + LOGGER); + assertDoesNotThrow(() -> ServiceLoaderUtil.safeStream( + BetterService.class, + ServiceLoader.load(BetterService.class, getClass().getClassLoader()), + LOGGER) + .forEach(services::add)); + assertThat(services).hasSize(1); + services.clear(); + assertDoesNotThrow(() -> ServiceLoaderUtil.safeStream( + PropertySource.class, + ServiceLoader.load(PropertySource.class, getClass().getClassLoader()), + LOGGER) + .forEach(services::add)); + assertThat(services).hasSize(3); + } + + @Test + @UsingStatusListener + void testBrokenServiceFile(final ListStatusListener listener) { + final List services = new ArrayList<>(); + assertDoesNotThrow(() -> ServiceLoaderUtil.safeStream( + Service.class, + ServiceLoader.load(Service.class, getClass().getClassLoader()), + LOGGER) + .forEach(services::add)); + assertEquals(2, services.size()); + // A warning for each broken service + final List errors = listener.findStatusData(Level.WARN) + .map(StatusData::getThrowable) + .collect(Collectors.toList()); + assertThat(errors.stream().map(Throwable::getMessage)) + .allMatch(message -> message.endsWith("invalid.Service not found") + || message.endsWith("org.apache.logging.log4j.Logger not a subtype") + || message.endsWith("Truncated class file")); + assertThat(errors.stream().>map(Throwable::getClass)) + .containsExactlyInAnyOrder( + ServiceConfigurationError.class, ServiceConfigurationError.class, ClassFormatError.class); + } + + @Test + void testOsgiUnavailable() { + // OSGI classes are present... + assertDoesNotThrow(() -> Class.forName("org.osgi.framework.FrameworkUtil")); + // ...but we don't run in an OSGI container + assertThat(OsgiServiceLocator.isAvailable()) + .as("Running in OSGI container") + .isFalse(); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java new file mode 100644 index 00000000000..e5709a6a547 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java @@ -0,0 +1,1109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.apache.logging.log4j.test.junit.SerialUtil.deserialize; +import static org.apache.logging.log4j.test.junit.SerialUtil.serialize; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +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.jupiter.api.Test; + +/** + * Tests the SortedArrayStringMap class. + */ +class SortedArrayStringMapTest { + + @Test + void testConstructorDisallowsNegativeCapacity() { + assertThrows(IllegalArgumentException.class, () -> new SortedArrayStringMap(-1)); + } + + @Test + void testConstructorAllowsZeroCapacity() { + final SortedArrayStringMap sortedArrayStringMap = new SortedArrayStringMap(0); + assertEquals(0, sortedArrayStringMap.size()); + } + + @Test + void testConstructorIgnoresNull() { + assertEquals(0, new SortedArrayStringMap((SortedArrayStringMap) null).size()); + } + + @Test + void testConstructorNonStringKeys() { + final Map map = new HashMap<>(1); + map.put(Long.MAX_VALUE, 1); + map.put(null, null); + final SortedArrayStringMap sMap = new SortedArrayStringMap((Map) map); + assertEquals(1, (int) sMap.getValue(Long.toString(Long.MAX_VALUE))); + assertEquals((Integer) null, sMap.getValue(null)); + } + + @Test + void testToString() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + assertEquals("{3=3value, B=Bvalue, a=avalue}", original.toString()); + } + + @Test + void testSerialization() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", null); // null may be treated differently + original.putValue("3", "3value"); + + final byte[] binary = serialize(original); + final SortedArrayStringMap copy = deserialize(binary); + assertEquals(original, copy); + } + + @Test + void testSerializationOfEmptyMap() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + final byte[] binary = serialize(original); + final SortedArrayStringMap copy = deserialize(binary); + assertEquals(original, copy); + } + + @Test + void testSerializationOfNonSerializableValue() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("unserializable", new Object()); + + final byte[] binary = serialize(original); + final SortedArrayStringMap copy = deserialize(binary); + + final SortedArrayStringMap expected = new SortedArrayStringMap(); + expected.putValue("a", "avalue"); + expected.putValue("B", "Bvalue"); + expected.putValue("unserializable", null); + assertEquals(expected, copy); + } + + @Test + void testDeserializationOfUnknownClass() throws Exception { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("serializableButNotInClasspathOfDeserializer", new org.junit.runner.Result()); + original.putValue("zz", "last"); + + final File file = new File("target/SortedArrayStringMap.ser"); + try (final FileOutputStream fout = new FileOutputStream(file, false)) { + fout.write(serialize(original)); + fout.flush(); + } + final String classpath = createClassPath(SortedArrayStringMap.class, DeserializerHelper.class); + final Process process = new ProcessBuilder( + "java", + "-cp", + classpath, + DeserializerHelper.class.getName(), + file.getPath(), + "org.junit.runner.Result") + .start(); + final BufferedReader in = new BufferedReader(new InputStreamReader(process.getErrorStream())); + final int exitValue = process.waitFor(); + + file.delete(); + if (exitValue != 0) { + final StringBuilder sb = new StringBuilder(); + sb.append("DeserializerHelper exited with error code ").append(exitValue); + sb.append(". Classpath='").append(classpath); + sb.append("'. Process output: "); + int c = -1; + while ((c = in.read()) != -1) { + sb.append((char) c); + } + fail(sb.toString()); + } + } + + private String createClassPath(final Class... classes) throws Exception { + final StringBuilder result = new StringBuilder(); + for (final Class cls : classes) { + if (result.length() > 0) { + result.append(File.pathSeparator); + } + result.append(createClassPath(cls)); + } + return result.toString(); + } + + private String createClassPath(final Class cls) throws Exception { + final String resource = "/" + cls.getName().replace('.', '/') + ".class"; + final URL url = cls.getResource(resource); + String location = url.toString(); + if (location.startsWith("jar:")) { + location = location.substring("jar:".length(), location.indexOf('!')); + } + if (location.startsWith("file:/")) { + location = location.substring("file:/".length()); + } + if (location.endsWith(resource)) { + location = location.substring(0, location.length() - resource.length()); + } + if (!new File(location).exists()) { + location = File.separator + location; + } + location = URLDecoder.decode(location, Charset.defaultCharset().name()); // replace %20 with ' ' etc + return location.isEmpty() ? "." : location; + } + + @Test + void testPutAll() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putAll(original); + assertEquals(original, other); + + other.putValue("3", "otherValue"); + assertNotEquals(original, other); + + other.putValue("3", null); + assertNotEquals(original, other); + + other.putValue("3", "3value"); + assertEquals(original, other); + } + + @Test + void testPutAll_overwritesSameKeys2() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("a", "aa"); + other.putValue("c", "cc"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cc", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + } + + @Test + void testPutAll_nullKeyInLargeOriginal() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullORIG", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInSmallOriginal() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullORIG", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInSmallAdditional() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInLargeAdditional() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInBoth_LargeOriginal() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInBoth_SmallOriginal() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_overwritesSameKeys1() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("a", "aa"); + other.putValue("c", "cc"); + original.putAll(other); + + assertEquals(5, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cc", original.getValue("c")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + } + + @Test + 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("Object other than SortedArrayStringMap", original); + + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + assertEquals(original, original); // equal to itself + + other.putValue("a", "avalue"); + assertNotEquals(original, other); + + other.putValue("B", "Bvalue"); + assertNotEquals(original, other); + + 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 + void testToMap() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final Map expected = new HashMap<>(); + expected.put("a", "avalue"); + expected.put("B", "Bvalue"); + expected.put("3", "3value"); + + assertEquals(expected, original.toMap()); + + assertDoesNotThrow(() -> original.toMap().put("abc", "xyz"), "Expected map to be mutable"); + } + + @Test + void testPutAll_KeepsExistingValues() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + assertEquals(3, original.size(), "size"); + + // add empty context data + original.putAll(new SortedArrayStringMap()); + assertEquals(3, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "111"); + other.putValue("2", "222"); + other.putValue("3", "333"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("111", original.getValue("1")); + assertEquals("222", original.getValue("2")); + assertEquals("333", original.getValue("3")); + } + + @Test + void testPutAll_sizePowerOfTwo() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + original.putValue("d", "ddd"); + assertEquals(4, original.size(), "size"); + + // add empty context data + original.putAll(new SortedArrayStringMap()); + assertEquals(4, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + other.putValue("1", "111"); + other.putValue("2", "222"); + other.putValue("3", "333"); + other.putValue("4", "444"); + original.putAll(other); + + assertEquals(8, original.size(), "size after put other"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + assertEquals("111", original.getValue("1")); + assertEquals("222", original.getValue("2")); + assertEquals("333", original.getValue("3")); + assertEquals("444", original.getValue("4")); + } + + @Test + void testPutAll_largeAddition() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue(null, "nullVal"); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + original.putValue("d", "ddd"); + assertEquals(5, original.size(), "size"); + + final SortedArrayStringMap other = new SortedArrayStringMap(); + for (int i = 0; i < 500; i++) { + other.putValue(String.valueOf(i), String.valueOf(i)); + } + other.putValue(null, "otherVal"); + original.putAll(other); + + assertEquals(505, original.size(), "size after put other"); + assertEquals("otherVal", original.getValue(null)); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + for (int i = 0; i < 500; i++) { + assertEquals(String.valueOf(i), original.getValue(String.valueOf(i))); + } + } + + @Test + void testPutAllSelfDoesNotModify() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + assertEquals(3, original.size(), "size"); + + // putAll with self + original.putAll(original); + assertEquals(3, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + } + + @Test + void testConcurrentModificationBiConsumerPut() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, + () -> original.forEach((s, o) -> original.putValue("c", "other"))); + } + + @Test + void testConcurrentModificationBiConsumerPutValue() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, + () -> original.forEach((s, o) -> original.putValue("c", "other"))); + } + + @Test + void testConcurrentModificationBiConsumerRemove() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.remove("a"))); + } + + @Test + void testConcurrentModificationBiConsumerClear() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.clear())); + } + + @Test + void testConcurrentModificationTriConsumerPut() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, + () -> original.forEach((s, o, o2) -> original.putValue("c", "other"), null)); + } + + @Test + void testConcurrentModificationTriConsumerPutValue() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, + () -> original.forEach((s, o, o2) -> original.putValue("c", "other"), null)); + } + + @Test + void testConcurrentModificationTriConsumerRemove() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, + () -> original.forEach((s, o, o2) -> original.remove("a"), null)); + } + + @Test + void testConcurrentModificationTriConsumerClear() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + assertThrows( + ConcurrentModificationException.class, () -> original.forEach((s, o, o2) -> original.clear(), null)); + } + + @Test + void testInitiallyNotFrozen() { + assertFalse(new SortedArrayStringMap().isFrozen()); + } + + @Test + void testIsFrozenAfterCallingFreeze() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + assertFalse(original.isFrozen(), "before freeze"); + original.freeze(); + assertTrue(original.isFrozen(), "after freeze"); + } + + @Test + void testFreezeProhibitsPutValue() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.freeze(); + assertThrows(UnsupportedOperationException.class, () -> original.putValue("a", "aaa")); + } + + @Test + void testFreezeProhibitsRemove() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("b", "bbb"); + original.freeze(); + assertThrows( + UnsupportedOperationException.class, + () -> original.remove("b")); // existing key: modifies the collection + } + + @Test + void testFreezeAllowsRemoveOfNonExistingKey() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("b", "bbb"); + original.freeze(); + assertDoesNotThrow(() -> original.remove("a")); + } + + @Test + void testFreezeAllowsRemoveIfEmpty() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.freeze(); + assertDoesNotThrow(() -> original.remove("a")); + } + + @Test + void testFreezeProhibitsClear() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "aaa"); + original.freeze(); + assertThrows(UnsupportedOperationException.class, original::clear); + } + + @Test + void testFreezeAllowsClearIfEmpty() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.freeze(); + assertDoesNotThrow(original::clear); + } + + @Test + void testPutInsertsInAlphabeticOrder() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + + assertEquals("avalue", original.getValue("a")); + assertEquals("avalue", original.getValueAt(2)); + + assertEquals("Bvalue", original.getValue("B")); + assertEquals("Bvalue", original.getValueAt(1)); + + assertEquals("3value", original.getValue("3")); + assertEquals("3value", original.getValueAt(0)); + + assertEquals("cvalue", original.getValue("c")); + assertEquals("cvalue", original.getValueAt(3)); + + assertEquals("dvalue", original.getValue("d")); + assertEquals("dvalue", original.getValueAt(4)); + } + + @Test + void testPutValueInsertsInAlphabeticOrder() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + + assertEquals("avalue", original.getValue("a")); + assertEquals("avalue", original.getValueAt(2)); + + assertEquals("Bvalue", original.getValue("B")); + assertEquals("Bvalue", original.getValueAt(1)); + + assertEquals("3value", original.getValue("3")); + assertEquals("3value", original.getValueAt(0)); + + assertEquals("cvalue", original.getValue("c")); + assertEquals("cvalue", original.getValueAt(3)); + + assertEquals("dvalue", original.getValue("d")); + assertEquals("dvalue", original.getValueAt(4)); + } + + @Test + void testNullKeysAllowed() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + assertEquals(5, original.size()); + assertEquals("{3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); + + original.putValue(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); + + original.putValue(null, "otherNullvalue"); + assertEquals("{null=otherNullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); + assertEquals(6, original.size()); + + original.putValue(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); + + original.putValue(null, "abc"); + assertEquals(6, original.size()); + assertEquals("{null=abc, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); + } + + @Test + void testNullKeysCopiedToAsMap() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + assertEquals(5, original.size()); + + final HashMap expected = new HashMap<>(); + expected.put("a", "avalue"); + expected.put("B", "Bvalue"); + expected.put("3", "3value"); + expected.put("c", "cvalue"); + expected.put("d", "dvalue"); + assertEquals(expected, original.toMap(), "initial"); + + original.putValue(null, "nullvalue"); + expected.put(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key"); + + original.putValue(null, "otherNullvalue"); + expected.put(null, "otherNullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value2"); + + original.putValue(null, "nullvalue"); + expected.put(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value1 again"); + + original.putValue(null, "abc"); + expected.put(null, "abc"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value3"); + } + + @Test + void testRemove() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertEquals("avalue", original.getValue("a")); + + original.remove("a"); + assertEquals(0, original.size()); + assertNull(original.getValue("a"), "no a val"); + + original.remove("B"); + assertEquals(0, original.size()); + assertNull(original.getValue("B"), "no B val"); + } + + @Test + void testRemoveNullsOutRemovedSlot() throws Exception { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("b", "bvalue"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + original.remove("a"); + original.remove("b"); + original.remove("c"); + original.remove("d"); + assertNull(original.getValueAt(0)); + + // ensure slots in the values array are nulled out + final Field f = SortedArrayStringMap.class.getDeclaredField("values"); + f.setAccessible(true); + final Object[] values = (Object[]) f.get(original); + assertAll(Arrays.stream(values).map(value -> () -> assertNull(value))); + } + + @Test + void testRemoveWhenFull() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("b", "bvalue"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); // default capacity = 4 + original.remove("d"); + } + + @Test + void testNullValuesArePreserved() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertEquals("avalue", original.getValue("a")); + + original.putValue("a", null); + assertEquals(1, original.size()); + assertNull(original.getValue("a"), "no a val"); + + original.putValue("B", null); + assertEquals(2, original.size()); + assertNull(original.getValue("B"), "no B val"); + } + + @Test + void testGet() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + assertEquals("avalue", original.getValue("a")); + assertEquals("Bvalue", original.getValue("B")); + assertEquals("3value", original.getValue("3")); + + original.putValue("0", "0value"); + assertEquals("0value", original.getValue("0")); + assertEquals("3value", original.getValue("3")); + assertEquals("Bvalue", original.getValue("B")); + assertEquals("avalue", original.getValue("a")); + } + + @Test + void testGetValue_GetValueAt() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + assertEquals("avalue", original.getValue("a")); + assertEquals("avalue", original.getValueAt(2)); + + assertEquals("Bvalue", original.getValue("B")); + assertEquals("Bvalue", original.getValueAt(1)); + + assertEquals("3value", original.getValue("3")); + assertEquals("3value", original.getValueAt(0)); + + original.putValue("0", "0value"); + assertEquals("0value", original.getValue("0")); + assertEquals("0value", original.getValueAt(0)); + assertEquals("3value", original.getValue("3")); + assertEquals("3value", original.getValueAt(1)); + assertEquals("Bvalue", original.getValue("B")); + assertEquals("Bvalue", original.getValueAt(2)); + assertEquals("avalue", original.getValue("a")); + assertEquals("avalue", original.getValueAt(3)); + } + + @Test + void testClear() throws Exception { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + assertEquals(3, original.size()); + + original.clear(); + assertEquals(0, original.size()); + + // ensure slots in the values array are nulled out + final Field f = SortedArrayStringMap.class.getDeclaredField("values"); + f.setAccessible(true); + final Object[] values = (Object[]) f.get(original); + assertAll(Arrays.stream(values).map(value -> () -> assertNull(value))); + } + + @Test + void testIndexOfKey() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + assertEquals(0, original.indexOfKey("a")); + + original.putValue("B", "Bvalue"); + assertEquals(1, original.indexOfKey("a")); + assertEquals(0, original.indexOfKey("B")); + + original.putValue("3", "3value"); + assertEquals(2, original.indexOfKey("a")); + assertEquals(1, original.indexOfKey("B")); + assertEquals(0, original.indexOfKey("3")); + + original.putValue("A", "AAA"); + assertEquals(3, original.indexOfKey("a")); + assertEquals(2, original.indexOfKey("B")); + assertEquals(1, original.indexOfKey("A")); + assertEquals(0, original.indexOfKey("3")); + + original.putValue("C", "CCC"); + assertEquals(4, original.indexOfKey("a")); + assertEquals(3, original.indexOfKey("C")); + assertEquals(2, original.indexOfKey("B")); + assertEquals(1, original.indexOfKey("A")); + assertEquals(0, original.indexOfKey("3")); + + original.putValue("2", "222"); + assertEquals(5, original.indexOfKey("a")); + assertEquals(4, original.indexOfKey("C")); + assertEquals(3, original.indexOfKey("B")); + assertEquals(2, original.indexOfKey("A")); + assertEquals(1, original.indexOfKey("3")); + assertEquals(0, original.indexOfKey("2")); + } + + @Test + void testContainsKey() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + 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(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); + + original.putValue("B", "Bvalue"); + 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(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); + + original.putValue("A", "AAA"); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertTrue(original.containsKey("A"), "A"); + } + + @Test + void testGetValueAt() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + assertEquals("a", original.getKeyAt(0)); + assertEquals("avalue", original.getValueAt(0)); + + original.putValue("B", "Bvalue"); + assertEquals("B", original.getKeyAt(0)); + assertEquals("Bvalue", original.getValueAt(0)); + assertEquals("a", original.getKeyAt(1)); + assertEquals("avalue", original.getValueAt(1)); + + original.putValue("3", "3value"); + assertEquals("3", original.getKeyAt(0)); + assertEquals("3value", original.getValueAt(0)); + assertEquals("B", original.getKeyAt(1)); + assertEquals("Bvalue", original.getValueAt(1)); + assertEquals("a", original.getKeyAt(2)); + assertEquals("avalue", original.getValueAt(2)); + } + + @Test + void testSizeAndIsEmpty() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + assertEquals(0, original.size()); + assertTrue(original.isEmpty(), "initial"); + + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.putValue("B", "Bvalue"); + assertEquals(2, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.putValue("3", "3value"); + assertEquals(3, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("B"); + assertEquals(2, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("3"); + assertEquals(1, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("a"); + assertEquals(0, original.size()); + assertTrue(original.isEmpty(), "size=" + original.size()); + } + + @Test + void testForEachBiConsumer() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + original.forEach(new BiConsumer() { + int count = 0; + + @Override + public void accept(final String key, final String value) { + assertEquals(key, original.getKeyAt(count), "key"); + assertEquals(value, original.getValueAt(count), "val"); + count++; + assertTrue(count <= original.size(), "count should not exceed size but was " + count); + } + }); + } + + static class State { + SortedArrayStringMap data; + int count; + } + + 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 + void testForEachTriConsumer() { + final SortedArrayStringMap original = new SortedArrayStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final State state = new State(); + state.data = original; + original.forEach(COUNTER, state); + assertEquals(state.count, original.size()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorTestIT.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorTestIT.java new file mode 100644 index 00000000000..e65755bba12 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorTestIT.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.util; + +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.security.Permission; +import java.util.Deque; +import org.apache.logging.log4j.test.junit.SecurityManagerTestRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Tests https://github.com/apache/logging-log4j2/pull/1214. + *

+ * 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 StackLocator + * @see SecurityManager + * @see System#setSecurityManager(SecurityManager) + */ +@ResourceLock("java.lang.SecurityManager") +public class StackLocatorTestIT { + @Rule + public final SecurityManagerTestRule rule = new SecurityManagerTestRule(new TestSecurityManager()); + + /** + * Always throws a SecurityException for any reques to create a new SecurityManager + */ + private static class TestSecurityManager extends SecurityManager { + @Override + public void checkPermission(final Permission permission) { + if ("createSecurityManager".equals(permission.getName())) { + throw new SecurityException(); + } + } + } + + @Test + public void testGetCurrentStacktraceSlowPath() { + final StackLocator stackLocator = StackLocator.getInstance(); + final Deque> classes = stackLocator.getCurrentStackTrace(); + assertSame(StackLocator.class, classes.getFirst()); + } +} 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..549e1c9c26f --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.Deque; +import java.util.Stack; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; + +/** + * Tests {@link StackLocatorUtil}. + */ +class StackLocatorUtilTest { + + @Test + @EnabledOnJre(JRE.JAVA_8) + void testStackTraceEquivalence() throws Exception { + // Frame 8 is a hidden frame and does not show in the stacktrace + for (int i = 1; i < 8; i++) { + final Class expected = (Class) Class.forName("sun.reflect.Reflection") + .getMethod("getCallerClass", int.class) + .invoke(null, 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 + void testGetCallerClass() { + final Class expected = StackLocatorUtilTest.class; + final Class actual = StackLocatorUtil.getCallerClass(1); + assertSame(expected, actual); + } + + @Test + void testGetCallerClassLoader() { + assertSame(StackLocatorUtilTest.class.getClassLoader(), StackLocatorUtil.getCallerClassLoader(1)); + } + + @Test + void testGetCallerClassNameViaStackTrace() throws Exception { + final Class expected = StackLocatorUtilTest.class; + final Class actual = Class.forName(new Throwable().getStackTrace()[0].getClassName()); + assertSame(expected, actual); + } + + @Test + void testGetCurrentStackTrace() { + final Deque> classes = StackLocatorUtil.getCurrentStackTrace(); + final Stack> reversed = new Stack<>(); + reversed.ensureCapacity(classes.size()); + while (!classes.isEmpty()) { + reversed.push(classes.removeLast()); + } + while (reversed.peek() != StackLocatorUtil.class) { + reversed.pop(); + } + reversed.pop(); // ReflectionUtil + assertSame(StackLocatorUtilTest.class, reversed.pop()); + } + + @Test + void testTopElementInStackTrace() { + final StackLocator stackLocator = StackLocator.getInstance(); + final Deque> classes = stackLocator.getCurrentStackTrace(); + // Removing private class in "PrivateSecurityManagerStackTraceUtil" + classes.removeFirst(); + assertSame(PrivateSecurityManagerStackTraceUtil.class, classes.getFirst()); + } + + @Test + void testGetCallerClassViaName() { + final Class expected = TestMethodTestDescriptor.class; + final Class actual = + StackLocatorUtil.getCallerClass("org.junit.platform.engine.support.hierarchical.ThrowableCollector"); + // 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 + void testGetCallerClassViaAnchorClass() { + final Class expected = TestMethodTestDescriptor.class; + final Class actual = StackLocatorUtil.getCallerClass(ThrowableCollector.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 + void testLocateClass() { + final ClassLocator locator = new ClassLocator(); + final Class clazz = locator.locateClass(); + assertNotNull(clazz, "Could note 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..8722dd33e42 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests the StringBuilders class. + */ +class StringBuildersTest { + @Test + 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 + 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 + void escapeJsonCharactersCorrectly() { + final String jsonValueNotEscaped = "{\"field\n1\":\"value_1\"}"; + final 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(); + final String jsonValuePartiallyEscaped = "{\"field\n1\":\\\"value_1\\\"}"; + sb.append(jsonValueNotEscaped); + assertEquals(jsonValueNotEscaped, sb.toString()); + StringBuilders.escapeJson(sb, 10); + assertEquals(jsonValuePartiallyEscaped, sb.toString()); + } + + @Test + void escapeJsonCharactersISOControl() { + final String jsonValueNotEscaped = "{\"field\n1\":\"value" + (char) 0x8F + "_1\"}"; + final String jsonValueEscaped = "{\\\"field\\n1\\\":\\\"value\\u008F_1\\\"}"; + + final StringBuilder sb = new StringBuilder(); + sb.append(jsonValueNotEscaped); + assertEquals(jsonValueNotEscaped, sb.toString()); + StringBuilders.escapeJson(sb, 0); + assertEquals(jsonValueEscaped, sb.toString()); + } + + @Test + void escapeXMLCharactersCorrectly() { + final String xmlValueNotEscaped = "<\"Salt&Peppa'\">"; + final String xmlValueEscaped = "<"Salt&Peppa'">"; + + final 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..66c9b956a35 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.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.util; + +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 java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import org.junit.jupiter.api.Test; + +/** + * Tests {@linkStrings}. + */ +class StringsTest { + + @Test + 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")); + } + + /** + * A sanity test to make sure a typo does not mess up {@link Strings#EMPTY}. + */ + @Test + void testEMPTY() { + assertEquals("", Strings.EMPTY); + assertEquals(0, Strings.EMPTY.length()); + } + + @Test + 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")); + } + + @Test + void testIsEmpty() { + assertTrue(Strings.isEmpty(null)); + assertTrue(Strings.isEmpty("")); + assertFalse(Strings.isEmpty(" ")); + assertFalse(Strings.isEmpty("a")); + } + + @Test + 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 + 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 + void testQuote() { + assertEquals("'Q'", Strings.quote("Q")); + } + + @Test + void testToLowerCase() { + assertEquals("a", Strings.toRootLowerCase("A")); + assertEquals("a", Strings.toRootLowerCase("a")); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java new file mode 100644 index 00000000000..36835cc8350 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +/** + * Prints system properties to the console. + */ +public class SystemPropertiesMain { + + /** + * Prints system properties to the console. + * + * @param args + * unused + */ + public static void main(final String[] args) { + @SuppressWarnings("unchecked") + final Enumeration keyEnum = + (Enumeration) System.getProperties().propertyNames(); + final List list = new ArrayList<>(); + while (keyEnum.hasMoreElements()) { + list.add(keyEnum.nextElement()); + } + Collections.sort(list); + for (final String key : list) { + System.out.println(key + " = " + System.getProperty(key)); + } + } +} 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..715f6a44b60 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceSecurityManagerIT.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.util; + +import static org.junit.Assert.assertNull; + +import java.security.Permission; +import java.util.PropertyPermission; +import org.apache.logging.log4j.test.junit.SecurityManagerTestRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Test related to https://issues.apache.org/jira/browse/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") +public class SystemPropertiesPropertySourceSecurityManagerIT { + + @Rule + public final SecurityManagerTestRule rule = new SecurityManagerTestRule(new TestSecurityManager()); + + /** + * 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 + && !permission.getName().equals("java.util.secureRandomSeed")) { + throw new SecurityException("Unexpected permission: " + permission); + } + } + } + + /** + * 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() { + final PropertiesUtil propertiesUtil = new PropertiesUtil("src/test/resources/PropertiesUtilTest.properties"); + assertNull(propertiesUtil.getStringProperty("a.1")); + } +} 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..4aba702bd01 --- /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.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-2276. + */ +@Tag("concurrency") +@ResourceLock(Resources.SYSTEM_PROPERTIES) +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 + void testMultiThreadedAccess() throws InterruptedException, ExecutionException { + final ExecutorService threadPool = Executors.newSingleThreadExecutor(); + try { + final 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-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java new file mode 100644 index 00000000000..2ce60cd0552 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +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; + +/** + * Tests the Unbox class. + */ +@ResourceLock(Resources.SYSTEM_PROPERTIES) +class Unbox1Test { + @BeforeAll + static void beforeClass() { + System.clearProperty("log4j.unbox.ringbuffer.size"); + } + + @Test + void testBoxClaimsItHas32Slots() { + assertEquals(32, Unbox.getRingbufferSize()); + } + + @Test + void testBoxHas32Slots() { + final int MAX = 32; + 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) + "]"); + } + } + } + + @Test + void testBoxBoolean() { + assertEquals("true", Unbox.box(true).toString()); + assertEquals("false", Unbox.box(false).toString()); + } + + @Test + void testBoxByte() { + assertEquals("0", Unbox.box((byte) 0).toString()); + assertEquals("1", Unbox.box((byte) 1).toString()); + assertEquals("127", Unbox.box((byte) 127).toString()); + assertEquals("-1", Unbox.box((byte) -1).toString()); + assertEquals("-128", Unbox.box((byte) -128).toString()); + } + + @Test + void testBoxChar() { + assertEquals("a", Unbox.box('a').toString()); + assertEquals("b", Unbox.box('b').toString()); + assertEquals("字", Unbox.box('字').toString()); + } + + @Test + void testBoxDouble() { + assertEquals("3.14", Unbox.box(3.14).toString()); + assertEquals( + Double.toString(Double.MAX_VALUE), Unbox.box(Double.MAX_VALUE).toString()); + assertEquals( + Double.toString(Double.MIN_VALUE), Unbox.box(Double.MIN_VALUE).toString()); + } + + @Test + void testBoxFloat() { + assertEquals("3.14", Unbox.box(3.14F).toString()); + assertEquals(Float.toString(Float.MAX_VALUE), Unbox.box(Float.MAX_VALUE).toString()); + assertEquals(Float.toString(Float.MIN_VALUE), Unbox.box(Float.MIN_VALUE).toString()); + } + + @Test + void testBoxInt() { + assertEquals("0", Unbox.box(0).toString()); + assertEquals("1", Unbox.box(1).toString()); + assertEquals("127", Unbox.box(127).toString()); + assertEquals("-1", Unbox.box(-1).toString()); + assertEquals("-128", Unbox.box(-128).toString()); + assertEquals( + Integer.toString(Integer.MAX_VALUE), + Unbox.box(Integer.MAX_VALUE).toString()); + assertEquals( + Integer.toString(Integer.MIN_VALUE), + Unbox.box(Integer.MIN_VALUE).toString()); + } + + @Test + void testBoxLong() { + assertEquals("0", Unbox.box(0L).toString()); + assertEquals("1", Unbox.box(1L).toString()); + assertEquals("127", Unbox.box(127L).toString()); + assertEquals("-1", Unbox.box(-1L).toString()); + assertEquals("-128", Unbox.box(-128L).toString()); + assertEquals(Long.toString(Long.MAX_VALUE), Unbox.box(Long.MAX_VALUE).toString()); + assertEquals(Long.toString(Long.MIN_VALUE), Unbox.box(Long.MIN_VALUE).toString()); + } + + @Test + void testBoxShort() { + assertEquals("0", Unbox.box((short) 0).toString()); + assertEquals("1", Unbox.box((short) 1).toString()); + assertEquals("127", Unbox.box((short) 127).toString()); + assertEquals("-1", Unbox.box((short) -1).toString()); + assertEquals("-128", Unbox.box((short) -128).toString()); + assertEquals(Short.toString(Short.MAX_VALUE), Unbox.box(Short.MAX_VALUE).toString()); + assertEquals(Short.toString(Short.MIN_VALUE), Unbox.box(Short.MIN_VALUE).toString()); + } + + @Test + void testBoxIsThreadLocal() throws Exception { + final StringBuilder[] probe = new StringBuilder[16 * 3]; + populate(0, probe); + final Thread t1 = new Thread(() -> populate(16, probe)); + t1.start(); + t1.join(); + 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 + j], + "probe[" + i + "]=" + probe[i] + ", probe[" + (i + j) + "]=" + probe[i + j]); + } + } + } + + private void populate(final int start, final StringBuilder[] probe) { + for (int i = start; i <= start + 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); + } + } +} 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..fd0fe73e4af --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +/** + * 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.*") +@ResourceLock(Resources.SYSTEM_PROPERTIES) +class Unbox2ConfigurableTest { + @BeforeAll + static void beforeClass() { + System.setProperty("log4j.unbox.ringbuffer.size", "65"); + } + + @AfterAll + 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<>()); + } + + @Test + void testBoxConfiguredTo128Slots() { + // next power of 2 that is 65 or more + assertEquals(128, Unbox.getRingbufferSize()); + } + + @Test + 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/java/org/apache/logging/log4j/util/internal/SerializationUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/internal/SerializationUtilTest.java new file mode 100644 index 00000000000..8b1d5d9dbba --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/internal/SerializationUtilTest.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.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SerializationUtilTest { + + static Stream arrays() { + return Stream.of( + Arguments.of(boolean[].class, boolean.class), + Arguments.of(char[].class, char.class), + Arguments.of(byte[].class, byte.class), + Arguments.of(short[].class, short.class), + Arguments.of(int[].class, int.class), + Arguments.of(long[].class, long.class), + Arguments.of(float[].class, float.class), + Arguments.of(double[].class, double.class), + Arguments.of(String.class, String.class), + Arguments.of(String[].class, String.class), + Arguments.of(String[][].class, String.class)); + } + + @ParameterizedTest + @MethodSource("arrays") + void stripArrayClass(final Class arrayClass, final Class componentClazz) { + assertThat(SerializationUtil.stripArray(arrayClass)).isEqualTo(componentClazz.getName()); + } + + @ParameterizedTest + @MethodSource("arrays") + void stripArrayString(final Class arrayClass, final Class componentClazz) { + assertThat(SerializationUtil.stripArray(arrayClass.getName())).isEqualTo(componentClazz.getName()); + } +} 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..6932845e3f3 --- /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 +log4j2.configurationFile=ok +log4j2.defaultStatusLevel=ok +log4j2.newLevel=ok + +# No characteristic prefix +log4j2.asyncLoggerTimeout=ok +log4j2.asyncLoggerConfigRingBufferSize=ok +log4j2.disableThreadContext=ok +log4j2.disableThreadContextStack=ok +log4j2.disableThreadContextMap=ok +log4j2.isThreadContextMapInheritable=ok diff --git a/log4j-api-test/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 new file mode 100644 index 00000000000..280fd54e8c1 --- /dev/null +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider @@ -0,0 +1,2 @@ +org.apache.logging.log4j.TestProvider +org.apache.logging.log4j.util.ProviderUtilTest.LocalProvider \ No newline at end of file diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.test.BetterService b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.test.BetterService new file mode 100644 index 00000000000..590e752c763 --- /dev/null +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.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.test.Service2 diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.test.Service b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.test.Service new file mode 100644 index 00000000000..5b9b8918afd --- /dev/null +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.test.Service @@ -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. + +# A correct entry +org.apache.logging.log4j.test.Service1 + +# Simulates a service retrieved from the server's classloader in a servlet environment. +# The class exists but does not extend org.apache.logging.log4j.test.Service +org.apache.logging.log4j.Logger + +# Simulates a broken service. +# Should cause a ClassNotFoundError +invalid.Service + +# Causes an exception not caught by ServiceLoader +org.apache.logging.log4j.test.ForceLinkageError + +# Another correct service +org.apache.logging.log4j.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 92% rename from log4j-api/src/test/resources/MF_en_US.properties rename to log4j-api-test/src/test/resources/MF_en_US.properties index c3c2802d525..b838a1db4e3 100644 --- a/log4j-api/src/test/resources/MF_en_US.properties +++ b/log4j-api-test/src/test/resources/MF_en_US.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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=This is the English, US test. hello_world=Hello world. msg1=This is test number {0} with string argument {1}. diff --git a/log4j-api/src/test/resources/MF_fr.properties b/log4j-api-test/src/test/resources/MF_fr.properties similarity index 92% rename from log4j-api/src/test/resources/MF_fr.properties rename to log4j-api-test/src/test/resources/MF_fr.properties index 25b878aebc6..2f15f638f8f 100644 --- a/log4j-api/src/test/resources/MF_fr.properties +++ b/log4j-api-test/src/test/resources/MF_fr.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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 France. hello_world=Bonjour la France. msg1=Ceci est le test numero {0} contenant l''argument {1}. 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 92% rename from log4j-api/src/test/resources/MF_fr_CH.properties rename to log4j-api-test/src/test/resources/MF_fr_CH.properties index ba9b1ffbfc6..92db1c204f4 100644 --- a/log4j-api/src/test/resources/MF_fr_CH.properties +++ b/log4j-api-test/src/test/resources/MF_fr_CH.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,5 +13,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. +# test=Ceci est le test en francais pour la p'tite Suisse. hello world=Salut le monde. 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..b6199c3c0c2 --- /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-test/src/test/resources/PropertiesUtilTest.properties b/log4j-api-test/src/test/resources/PropertiesUtilTest.properties new file mode 100644 index 00000000000..16920bd3623 --- /dev/null +++ b/log4j-api-test/src/test/resources/PropertiesUtilTest.properties @@ -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. +# + +a.1 = 1 +a.2 = 2 +a.3 = 3 +b.1 = 1 +b.2 = 2 +b.3 = 3 +c.1.1 = 1 +c.1.2 = 2 +c.1.3 = 3 +dd.1 = 1 +dd.2 = 2 +dd.3 = 3 + +# Dotless entry, should be ignored +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 92% rename from log4j-api/src/test/resources/SF_en_US.properties rename to log4j-api-test/src/test/resources/SF_en_US.properties index abcd57883ea..7d2afb171e4 100644 --- a/log4j-api/src/test/resources/SF_en_US.properties +++ b/log4j-api-test/src/test/resources/SF_en_US.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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=This is the English, US test. hello_world=Hello world. msg1=This is test number %1s with string argument %2s. diff --git a/log4j-api/src/test/resources/SF_fr.properties b/log4j-api-test/src/test/resources/SF_fr.properties similarity index 92% rename from log4j-api/src/test/resources/SF_fr.properties rename to log4j-api-test/src/test/resources/SF_fr.properties index 028c8d9d95b..ea7eb9e6ecf 100644 --- a/log4j-api/src/test/resources/SF_fr.properties +++ b/log4j-api-test/src/test/resources/SF_fr.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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 France. hello_world=Bonjour la France. msg1=Ceci est le test numero %1s contenant l''argument %2s. 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 92% rename from log4j-api/src/test/resources/SF_fr_CH.properties rename to log4j-api-test/src/test/resources/SF_fr_CH.properties index ba9b1ffbfc6..92db1c204f4 100644 --- a/log4j-api/src/test/resources/SF_fr_CH.properties +++ b/log4j-api-test/src/test/resources/SF_fr_CH.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,5 +13,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. +# test=Ceci est le test en francais pour la p'tite Suisse. hello world=Salut le monde. diff --git a/log4j-api-test/src/test/resources/StatusLoggerPropertiesUtilDoubleTest.properties b/log4j-api-test/src/test/resources/StatusLoggerPropertiesUtilDoubleTest.properties new file mode 100644 index 00000000000..923acfe587f --- /dev/null +++ b/log4j-api-test/src/test/resources/StatusLoggerPropertiesUtilDoubleTest.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. +# + +# +# This file only exists to test if `StatusLogger.PropertiesUtilDouble` indeed loads the resource at the root directory. +# Hence, don't place this file under a subdirectory! +# +foo=bar 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..ab081c817a7 --- /dev/null +++ b/log4j-api-test/src/test/resources/log4j2.system.properties @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +Application=Log4j diff --git a/log4j-api-test/src/test/resources/org/apache/logging/log4j/test/ForceLinkageError.class b/log4j-api-test/src/test/resources/org/apache/logging/log4j/test/ForceLinkageError.class new file mode 100644 index 00000000000..e69de29bb2d diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml index 2ddbde86382..4b0525466df 100644 --- a/log4j-api/pom.xml +++ b/log4j-api/pom.xml @@ -1,131 +1,92 @@ + 4.0.0 + org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent + log4j-api - jar + Apache Log4j API - The Apache Log4j API + + The logging API of the Log4j project. + Library and application code can log through this API. + It contains a simple built-in implementation (`SimpleLogger`) for trivial use cases. + Production applications are recommended to use Log4j API in combination with a fully-fledged implementation, such as Log4j Core. + - ${basedir}/.. - API Documentation - /api + false + + + true + + org.apache.logging.log4j + + + !sun.reflect, + + org.jspecify.*;resolution:=optional + + + + java.sql;static=true, + + java.management;static=true, + + org.jspecify;transitive=false + + + - org.apache.logging.log4j - log4j-api-java9 + org.jspecify + jspecify provided - zip - - - - org.apache.felix - org.apache.felix.framework - test + org.osgi org.osgi.core 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 + generate-sources ${project.build.directory}/log4j-api-java9 @@ -134,220 +95,37 @@ + org.apache.maven.plugins - maven-compiler-plugin - - - default-compile - - - 1.7 - 1.7 - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - 2C - true - - - - org.apache.maven.plugins - maven-jar-plugin - - - default-jar - - jar - - - - ${manifestfile} - - ${project.name} - ${project.version} - ${project.organization.name} - ${project.name} - ${project.version} - ${project.organization.name} - 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 + maven-dependency-plugin + unpack-classes - process + unpack + prepare-package - false + + + org.apache.logging.log4j + log4j-api-java9 + ${project.version} + zip + false + + + **/*.class + **/*.java + ${project.build.directory} + false + true - - 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} - - - - changes-report - - - - - %URL%/show_bug.cgi?id=%ISSUE% - true - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${checkstyle.plugin.version} - - - ${log4jParentDir}/checkstyle.xml - ${log4jParentDir}/checkstyle-suppressions.xml - false - basedir=${basedir} - licensedir=${log4jParentDir}/checkstyle-header.txt - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc.plugin.version} - - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
- 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.

]]>
- - false - true - - http://www.osgi.org/javadoc/r4v43/core/ - -
- - - non-aggregate - - javadoc - - - -
- - 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 - - jxr - - - - aggregate - - aggregate - - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${pmd.plugin.version} - - ${maven.compiler.target} - - -
-
- 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..38a11fe800d --- /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. + * + * @since 2.19.0 + */ +public interface BridgeAware { + + /** + * To set fully qualified class name of the entry point to the logging system. This + * class will not appear in the location information. + * + * @param fqcn fully qualified class name + */ + void setEntryPoint(final String fqcn); +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java index 2b1a8426f96..f66c8144baa 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java @@ -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. */ package org.apache.logging.log4j; +import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -36,8 +36,7 @@ */ public class CloseableThreadContext { - private CloseableThreadContext() { - } + private CloseableThreadContext() {} /** * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when @@ -100,17 +99,19 @@ public static CloseableThreadContext.Instance putAll(final Map v return new CloseableThreadContext.Instance().putAll(values); } + /** + * @since 2.6 + */ public static class Instance implements AutoCloseable { private int pushCount = 0; private final Map originalValues = new HashMap<>(); - private Instance() { - } + private Instance() {} /** - * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when - * the instance is closed. + * Pushes new diagnostic context information on to the Thread Context Stack. + * The information will be popped off when the instance is closed. * * @param message The new diagnostic context information. * @return the instance that will back out the changes when closed. @@ -122,8 +123,8 @@ public Instance push(final String message) { } /** - * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when - * the instance is closed. + * Pushes new diagnostic context information on to the Thread Context Stack. + * The information will be popped off when the instance is closed. * * @param message The new diagnostic context information. * @param args Parameters for the message. @@ -137,8 +138,8 @@ public Instance push(final String message, final Object[] args) { /** * Populates the Thread Context Map with the supplied key/value pair. Any existing key in the - * {@link ThreadContext} will be replaced with the supplied value, and restored back to their original value when - * the instance is closed. + * {@link ThreadContext} will be replaced with the supplied value, + * and restored back to their original value when the instance is closed. * * @param key The key to be added * @param value The value to be added @@ -155,8 +156,8 @@ public Instance put(final String key, final String value) { /** * Populates the Thread Context Map with the supplied key/value pairs. Any existing keys in the - * {@link ThreadContext} will be replaced with the supplied values, and restored back to their original value when - * the instance is closed. + * {@link ThreadContext} will be replaced with the supplied values, + * and restored back to their original value when the instance is closed. * * @param values The map of key/value pairs to be added * @return a new instance that will back out the changes when closed. @@ -194,7 +195,8 @@ public Instance pushAll(final List messages) { * Values pushed to the {@link ThreadContext} stack will be popped off. *

*

- * Values put on the {@link ThreadContext} map will be removed, or restored to their original values it they already existed. + * Values put on the {@link ThreadContext} map will be removed, + * or restored to their original values it they already existed. *

*/ @Override @@ -204,16 +206,22 @@ public void close() { } private void closeMap() { - for (final Iterator> it = originalValues.entrySet().iterator(); it.hasNext(); ) { - final Map.Entry entry = it.next(); + final Map valuesToReplace = new HashMap<>(originalValues.size()); + final List keysToRemove = new ArrayList<>(originalValues.size()); + for (final Map.Entry entry : originalValues.entrySet()) { final String key = entry.getKey(); final String originalValue = entry.getValue(); if (null == originalValue) { - ThreadContext.remove(key); + keysToRemove.add(key); } else { - ThreadContext.put(key, originalValue); + valuesToReplace.put(key, originalValue); } - it.remove(); + } + if (!valuesToReplace.isEmpty()) { + ThreadContext.putAll(valuesToReplace); + } + if (!keysToRemove.isEmpty()) { + ThreadContext.removeAll(keysToRemove); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/EventLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/EventLogger.java index 364b64020b9..f88dccce895 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/EventLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/EventLogger.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j; @@ -20,8 +20,12 @@ import org.apache.logging.log4j.spi.ExtendedLogger; /** - * Logs "Events" that are represented as {@link StructuredDataMessage}. + * Convenience to log {@link StructuredDataMessage}s. + * + * @deprecated Deprecated since 2.24.0. + * {@link Logger} accepts {@link StructuredDataMessage}s, users should use to that instead. */ +@Deprecated public final class EventLogger { /** 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..47a6fc6367e 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 @@ -1,110 +1,134 @@ /* * 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.logging.log4j; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + import java.io.Serializable; -import java.util.Collection; -import java.util.Locale; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; - import org.apache.logging.log4j.spi.StandardLevel; import org.apache.logging.log4j.util.Strings; /** * 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)
  • - *
- * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Level names with description
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()); /** + * Category to be used by custom levels. + * * @since 2.1 */ public static final String CATEGORY = "Level"; - private static final ConcurrentMap LEVELS = new ConcurrentHashMap<>(); // SUPPRESS CHECKSTYLE - 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 +143,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(toRootUpperCase(name.trim()), this) != null) { throw new IllegalStateException("Level " + name + " has already been defined."); } } @@ -239,15 +263,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 = toRootUpperCase(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 +285,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(toRootUpperCase(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,22 +317,17 @@ 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(toRootUpperCase(name.trim())); return level == null ? defaultLevel : level; } - private static String toUpperCase(final String name) { - return name.toUpperCase(Locale.ENGLISH); - } - /** * Return an array of all the Levels that have been registered. * * @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 +340,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 = toRootUpperCase(name.trim()); final Level level = LEVELS.get(levelName); if (level != null) { return level; @@ -338,7 +366,7 @@ public static > T valueOf(final Class enumType, final Strin } // for deserialization - protected Object readResolve() { + private Object readResolve() { return Level.valueOf(this.name); } } 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..2d5c18023b3 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.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; + +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. + * + * @since 2.13.0 + */ +public interface LogBuilder { + /** NOOP 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 {@code null} if no logging occurred. + * @since 2.20.0 + */ + 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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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. + * + * @since 2.13.1 + * @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 implementatoin does nothing. + * + * @since 2.14.1 + */ + 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..1cf86d26983 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 @@ -1,36 +1,30 @@ /* * 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.logging.log4j; import java.net.URI; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - +import org.apache.logging.log4j.internal.LogManagerStatus; 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.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; @@ -47,9 +41,12 @@ 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}. + * Log4j's property to set to the fully qualified class name of a custom implementation of + * {@link LoggerContextFactory}. + * @since 2.0.1 + * @deprecated Replaced since 2.24.0 with {@value org.apache.logging.log4j.spi.Provider#PROVIDER_PROPERTY_NAME}. */ + @Deprecated public static final String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory"; /** @@ -62,73 +59,21 @@ public class LogManager { // for convenience private static final String FQCN = LogManager.class.getName(); - private static volatile LoggerContextFactory factory; + private static volatile LoggerContextFactory factory = + ProviderUtil.getProvider().getLoggerContextFactory(); - /** + /* * 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(); - } - } + LogManagerStatus.setInitialized(true); } /** * Prevents instantiation */ - protected LogManager() { - } + protected LogManager() {} /** * Detects if a Logger with the specified name exists. This is a convenience method for porting from version 1. @@ -154,8 +99,8 @@ public static LoggerContext getContext() { try { return factory.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); } } @@ -173,8 +118,8 @@ public static LoggerContext getContext(final boolean currentContext) { try { return factory.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); } } @@ -193,8 +138,8 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c try { return factory.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); } } @@ -210,13 +155,13 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. * @return a LoggerContext. */ - public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, - final Object externalContext) { + public static LoggerContext getContext( + final ClassLoader loader, final boolean currentContext, final Object externalContext) { try { return factory.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); } } @@ -232,14 +177,14 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c * @param configLocation The URI for the configuration to use. * @return a LoggerContext. */ - public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, - final URI configLocation) { + public static LoggerContext getContext( + final ClassLoader loader, final boolean currentContext, final URI configLocation) { try { return factory.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, - null); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext( + FQCN, loader, null, currentContext, configLocation, null); } } @@ -256,14 +201,17 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c * @param configLocation The URI for the configuration to use. * @return a LoggerContext. */ - public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, - final Object externalContext, final URI configLocation) { + 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); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, externalContext, currentContext, - configLocation, null); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext( + FQCN, loader, externalContext, currentContext, configLocation, null); } } @@ -281,14 +229,18 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c * @param name The LoggerContext name. * @return a LoggerContext. */ - public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, - final Object externalContext, final URI configLocation, final String name) { + 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); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, externalContext, currentContext, - configLocation, name); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext( + FQCN, loader, externalContext, currentContext, configLocation, name); } } @@ -306,8 +258,8 @@ protected static LoggerContext getContext(final String fqcn, final boolean curre try { return factory.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); } } @@ -323,17 +275,16 @@ protected static LoggerContext getContext(final String fqcn, final boolean curre * be returned. If true then only a single LoggerContext will be returned. * @return a LoggerContext. */ - protected static LoggerContext getContext(final String fqcn, final ClassLoader loader, - final boolean currentContext) { + protected static LoggerContext getContext( + final String fqcn, final ClassLoader loader, final boolean currentContext) { try { return factory.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); } } - /** * Returns a LoggerContext * @@ -347,24 +298,29 @@ protected static LoggerContext getContext(final String fqcn, final ClassLoader l * @param configLocation The URI for the configuration to use. * @param name The LoggerContext name. * @return a LoggerContext. - */ - protected static LoggerContext getContext(final String fqcn, final ClassLoader loader, - final boolean currentContext, URI configLocation, String name) { + * @since 2.9.1 + */ + protected static LoggerContext getContext( + final String fqcn, + final ClassLoader loader, + final boolean currentContext, + final URI configLocation, + final String name) { try { return factory.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); } } /** * Shutdown using the LoggerContext appropriate for the caller of this method. * This is equivalent to calling {@code LogManager.shutdown(false)}. - * - * This call is synchronous and will block until shut down is complete. - * This may include flushing pending log events over network connections. - * + *

+ * This call is synchronous and will block until shut down is complete. This may include flushing pending log + * events over network connections. + *

* @since 2.6 */ public static void shutdown() { @@ -374,9 +330,10 @@ public static void shutdown() { /** * 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. + *

+ * 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. @@ -387,29 +344,46 @@ public static void shutdown() { * @since 2.6 */ public static void shutdown(final boolean currentContext) { - shutdown(getContext(currentContext)); + factory.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. + *

* - * 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) { + factory.shutdown(FQCN, null, currentContext, allContexts); + } + + /** + * Shutdown the logging system if the logging system supports it. + *

+ * This call is synchronous and will block until shut down is complete. This may include flushing pending log + * events over network connections. + *

* * @param context the LoggerContext. * @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. * @@ -479,8 +453,8 @@ public static Logger getFormatterLogger() { * @see StringFormatterMessageFactory */ public static Logger getFormatterLogger(final Class clazz) { - return getLogger(clazz != null ? clazz : StackLocatorUtil.getCallerClass(2), - StringFormatterMessageFactory.INSTANCE); + return getLogger( + clazz != null ? clazz : StackLocatorUtil.getCallerClass(2), StringFormatterMessageFactory.INSTANCE); } /** @@ -492,7 +466,7 @@ public static Logger getFormatterLogger(final Class clazz) { * Short-hand for {@code getLogger(value, StringFormatterMessageFactory.INSTANCE)} *

* - * @param value The value's whose class name should be used as the Logger name. + * @param value The value whose class name should be used as the Logger name. * @return The Logger, created with a {@link StringFormatterMessageFactory} * @throws UnsupportedOperationException if {@code value} is {@code null} and the calling class cannot be * determined. @@ -511,7 +485,8 @@ public static Logger getFormatterLogger(final Class clazz) { * @see StringFormatterMessageFactory */ public static Logger getFormatterLogger(final Object value) { - return getLogger(value != null ? value.getClass() : StackLocatorUtil.getCallerClass(2), + return getLogger( + value != null ? value.getClass() : StackLocatorUtil.getCallerClass(2), StringFormatterMessageFactory.INSTANCE); } @@ -542,8 +517,9 @@ public static Logger getFormatterLogger(final Object value) { * @see StringFormatterMessageFactory */ public static Logger getFormatterLogger(final String name) { - return name == null ? getFormatterLogger(StackLocatorUtil.getCallerClass(2)) : getLogger(name, - StringFormatterMessageFactory.INSTANCE); + return name == null + ? getFormatterLogger(StackLocatorUtil.getCallerClass(2)) + : getLogger(name, StringFormatterMessageFactory.INSTANCE); } private static Class callerClass(final Class clazz) { @@ -578,7 +554,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 +570,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); } /** @@ -658,8 +634,9 @@ public static Logger getLogger(final String name) { * @throws UnsupportedOperationException if {@code name} is {@code null} and the calling class cannot be determined. */ public static Logger getLogger(final String name, final MessageFactory messageFactory) { - return name != null ? getContext(false).getLogger(name, messageFactory) : getLogger( - StackLocatorUtil.getCallerClass(2), messageFactory); + return name != null + ? getContext(false).getLogger(name, messageFactory) + : getLogger(StackLocatorUtil.getCallerClass(2), messageFactory); } /** 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..f958d08127c 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 @@ -1,22 +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. */ 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; @@ -69,7 +70,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. *

@@ -77,39 +78,41 @@ public interface Logger { /** - * Logs an exception or error that has been caught to a specific logging level. + * Logs a {@link Throwable} that has been caught to a specific logging level. * * @param level The logging Level. - * @param t The Throwable. + * @param throwable the Throwable. */ - void catching(Level level, Throwable t); + void catching(Level level, Throwable throwable); /** - * Logs an exception or error that has been caught. Normally, one may wish to provide additional information with an - * exception while logging it; in these cases, one would not use this method. In other cases where simply logging - * the fact that an exception was swallowed somewhere (e.g., at the top of the stack trace in a {@code main()} - * method), this method is ideal for it. + * Logs a {@link Throwable} that has been caught at the {@link Level#ERROR ERROR} level. + * Normally, one may wish to provide additional information with an exception while logging it; + * in these cases, one would not use this method. + * In other cases where simply logging the fact that an exception was swallowed somewhere + * (e.g., at the top of the stack trace in a {@code main()} method), + * this method is ideal for it. * - * @param t The Throwable. + * @param throwable the Throwable. */ - void catching(Throwable t); + void catching(Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#DEBUG DEBUG} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void debug(Marker marker, Message msg); + void debug(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#DEBUG DEBUG} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void debug(Marker marker, Message msg, Throwable t); + void debug(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level with @@ -117,40 +120,42 @@ public interface Logger { * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void debug(Marker marker, MessageSupplier msgSupplier); + void debug(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void debug(Marker marker, MessageSupplier msgSupplier, Throwable t); + void debug(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#DEBUG DEBUG} level. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. + * @since 2.6 */ void debug(Marker marker, CharSequence message); /** * Logs a message CharSequence at the {@link Level#DEBUG DEBUG} level including the stack trace of the - * {@link Throwable} t passed as parameter. + * {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void debug(Marker marker, CharSequence message, Throwable t); + void debug(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#DEBUG DEBUG} level. @@ -162,13 +167,13 @@ public interface Logger { /** * Logs a message at the {@link Level#DEBUG DEBUG} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void debug(Marker marker, Object message, Throwable t); + void debug(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#DEBUG DEBUG} level. @@ -201,87 +206,89 @@ public interface Logger { /** * Logs a message at the {@link Level#DEBUG DEBUG} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void debug(Marker marker, String message, Throwable t); + void debug(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level with * the specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void debug(Marker marker, Supplier msgSupplier); + void debug(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void debug(Marker marker, Supplier msgSupplier, Throwable t); + void debug(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#DEBUG DEBUG} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void debug(Message msg); + void debug(Message message); /** * Logs a message with the specific Marker at the {@link Level#DEBUG DEBUG} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void debug(Message msg, Throwable t); + void debug(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void debug(MessageSupplier msgSupplier); + void debug(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level) including the - * stack trace of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack trace of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack trace. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void debug(MessageSupplier msgSupplier, Throwable t); + void debug(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#DEBUG DEBUG} level. * * @param message the message object to log. + * @since 2.6 */ void debug(CharSequence message); /** * Logs a CharSequence at the {@link Level#DEBUG DEBUG} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void debug(CharSequence message, Throwable t); + void debug(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#DEBUG DEBUG} level. @@ -292,12 +299,12 @@ public interface Logger { /** * Logs a message at the {@link Level#DEBUG DEBUG} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void debug(Object message, Throwable t); + void debug(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#DEBUG DEBUG} level. @@ -327,32 +334,32 @@ public interface Logger { /** * Logs a message at the {@link Level#DEBUG DEBUG} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void debug(String message, Throwable t); + void debug(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void debug(Supplier msgSupplier); + void debug(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#DEBUG DEBUG} level) including the - * stack trace of the {@link Throwable} t passed as parameter. + * stack trace of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void debug(Supplier msgSupplier, Throwable t); + void debug(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at debug level. @@ -360,6 +367,7 @@ public interface Logger { * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void debug(Marker marker, String message, Object p0); @@ -370,6 +378,7 @@ public interface Logger { * @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. + * @since 2.6 */ void debug(Marker marker, String message, Object p0, Object p1); @@ -381,6 +390,7 @@ public interface Logger { * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void debug(Marker marker, String message, Object p0, Object p1, Object p2); @@ -393,6 +403,7 @@ public interface Logger { * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -406,6 +417,7 @@ public interface Logger { * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -420,6 +432,7 @@ public interface Logger { * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -435,9 +448,10 @@ public interface Logger { * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void debug( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at debug level. @@ -452,8 +466,18 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void debug( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -470,9 +494,20 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void debug( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at debug level. @@ -489,15 +524,28 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void debug(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void debug( + Marker marker, + 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 with parameters at debug level. * * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0); @@ -507,6 +555,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @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. + * @since 2.6 */ void debug(String message, Object p0, Object p1); @@ -517,6 +566,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2); @@ -528,6 +578,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2, Object p3); @@ -540,6 +591,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -553,6 +605,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -567,6 +620,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -582,6 +636,7 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); @@ -598,8 +653,18 @@ void debug(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void debug( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -616,14 +681,25 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + 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 Since 2.6, use {@link #traceEntry()} instead which performs the same function. */ @Deprecated void entry(); @@ -645,25 +721,27 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 *

* * @param params The parameters to the method. + * @deprecated since 2.11.2, use {@link #traceEntry(String, Object...)} instead which performs the same function. */ + @Deprecated void entry(Object... params); /** * Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void error(Marker marker, Message msg); + void error(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void error(Marker marker, Message msg, Throwable t); + void error(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#ERROR ERROR} level with @@ -671,40 +749,42 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void error(Marker marker, MessageSupplier msgSupplier); + void error(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#ERROR ERROR} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void error(Marker marker, MessageSupplier msgSupplier, Throwable t); + void error(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#ERROR ERROR} level. * * @param marker the marker data specific to this log statement. * @param message the message CharSequence to log. + * @since 2.6 */ void error(Marker marker, CharSequence message); /** * Logs a CharSequence at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement. * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void error(Marker marker, CharSequence message, Throwable t); + void error(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#ERROR ERROR} level. @@ -716,13 +796,13 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement. * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void error(Marker marker, Object message, Throwable t); + void error(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#ERROR ERROR} level. @@ -755,87 +835,89 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement. * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void error(Marker marker, String message, Throwable t); + void error(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#ERROR ERROR} level with * the specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void error(Marker marker, Supplier msgSupplier); + void error(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#ERROR ERROR} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void error(Marker marker, Supplier msgSupplier, Throwable t); + void error(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void error(Message msg); + void error(Message message); /** * Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void error(Message msg, Throwable t); + void error(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#ERROR ERROR} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void error(MessageSupplier msgSupplier); + void error(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#ERROR ERROR} level) including the - * stack trace of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack trace of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack trace. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void error(MessageSupplier msgSupplier, Throwable t); + void error(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#ERROR ERROR} level. * * @param message the message CharSequence to log. + * @since 2.6 */ void error(CharSequence message); /** * Logs a CharSequence at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void error(CharSequence message, Throwable t); + void error(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#ERROR ERROR} level. @@ -846,12 +928,12 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void error(Object message, Throwable t); + void error(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#ERROR ERROR} level. @@ -881,32 +963,32 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#ERROR ERROR} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void error(String message, Throwable t); + void error(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#ERROR ERROR} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void error(Supplier msgSupplier); + void error(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#ERROR ERROR} level) including the - * stack trace of the {@link Throwable} t passed as parameter. + * stack trace of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void error(Supplier msgSupplier, Throwable t); + void error(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at error level. @@ -914,6 +996,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void error(Marker marker, String message, Object p0); @@ -924,6 +1007,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @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. + * @since 2.6 */ void error(Marker marker, String message, Object p0, Object p1); @@ -935,6 +1019,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void error(Marker marker, String message, Object p0, Object p1, Object p2); @@ -947,6 +1032,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -960,6 +1046,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -974,6 +1061,7 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -989,9 +1077,10 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void error( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at error level. @@ -1006,8 +1095,18 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void error( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -1024,9 +1123,20 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void error( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at error level. @@ -1043,15 +1153,28 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void error(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void error( + Marker marker, + 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 with parameters at error level. * * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void error(String message, Object p0); @@ -1061,6 +1184,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @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. + * @since 2.6 */ void error(String message, Object p0, Object p1); @@ -1071,6 +1195,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2); @@ -1082,6 +1207,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2, Object p3); @@ -1094,6 +1220,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -1107,6 +1234,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -1121,6 +1249,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -1136,6 +1265,7 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); @@ -1152,8 +1282,18 @@ void error(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void error( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -1170,13 +1310,24 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + 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 Since 2.6, use {@link #traceExit()} instead which performs the same function. */ @Deprecated void exit(); @@ -1191,7 +1342,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @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 Since 2.6, use {@link #traceExit(Object)} instead which performs the same function. */ @Deprecated R exit(R result); @@ -1200,18 +1351,18 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void fatal(Marker marker, Message msg); + void fatal(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void fatal(Marker marker, Message msg, Throwable t); + void fatal(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#FATAL FATAL} level with @@ -1219,40 +1370,42 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void fatal(Marker marker, MessageSupplier msgSupplier); + void fatal(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#FATAL FATAL} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void fatal(Marker marker, MessageSupplier msgSupplier, Throwable t); + void fatal(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#FATAL FATAL} level. * * @param marker The marker data specific to this log statement. * @param message the message CharSequence to log. + * @since 2.6 */ void fatal(Marker marker, CharSequence message); /** * Logs a CharSequence at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker The marker data specific to this log statement. * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void fatal(Marker marker, CharSequence message, Throwable t); + void fatal(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#FATAL FATAL} level. @@ -1264,13 +1417,13 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker The marker data specific to this log statement. * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void fatal(Marker marker, Object message, Throwable t); + void fatal(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#FATAL FATAL} level. @@ -1303,87 +1456,89 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker The marker data specific to this log statement. * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void fatal(Marker marker, String message, Throwable t); + void fatal(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#FATAL FATAL} level with * the specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void fatal(Marker marker, Supplier msgSupplier); + void fatal(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#FATAL FATAL} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void fatal(Marker marker, Supplier msgSupplier, Throwable t); + void fatal(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void fatal(Message msg); + void fatal(Message message); /** * Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void fatal(Message msg, Throwable t); + void fatal(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#FATAL FATAL} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void fatal(MessageSupplier msgSupplier); + void fatal(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#FATAL FATAL} level) including the - * stack trace of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack trace of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack trace. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void fatal(MessageSupplier msgSupplier, Throwable t); + void fatal(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#FATAL FATAL} level. * * @param message the message CharSequence to log. + * @since 2.6 */ void fatal(CharSequence message); /** * Logs a CharSequence at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void fatal(CharSequence message, Throwable t); + void fatal(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#FATAL FATAL} level. @@ -1394,12 +1549,12 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void fatal(Object message, Throwable t); + void fatal(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#FATAL FATAL} level. @@ -1429,32 +1584,32 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#FATAL FATAL} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void fatal(String message, Throwable t); + void fatal(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#FATAL FATAL} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void fatal(Supplier msgSupplier); + void fatal(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#FATAL FATAL} level) including the - * stack trace of the {@link Throwable} t passed as parameter. + * stack trace of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void fatal(Supplier msgSupplier, Throwable t); + void fatal(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at fatal level. @@ -1462,6 +1617,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0); @@ -1472,6 +1628,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @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. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0, Object p1); @@ -1483,6 +1640,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0, Object p1, Object p2); @@ -1495,6 +1653,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -1508,6 +1667,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -1522,6 +1682,7 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -1537,9 +1698,10 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void fatal( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at fatal level. @@ -1554,8 +1716,18 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void fatal( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -1572,9 +1744,20 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void fatal( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at fatal level. @@ -1591,15 +1774,28 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void fatal( + Marker marker, + 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 with parameters at fatal level. * * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0); @@ -1609,6 +1805,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @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. + * @since 2.6 */ void fatal(String message, Object p0, Object p1); @@ -1619,6 +1816,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2); @@ -1630,6 +1828,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2, Object p3); @@ -1642,6 +1841,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -1655,6 +1855,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -1669,6 +1870,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -1684,6 +1886,7 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); @@ -1700,8 +1903,18 @@ void fatal(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void fatal( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -1718,9 +1931,20 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + void fatal( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9); /** * Gets the Level associated with the Logger. @@ -1731,17 +1955,26 @@ 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. + *

+ * 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} */ 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.0 + */ + FlowMessageFactory getFlowMessageFactory(); + /** * Gets the logger name. * @@ -1753,18 +1986,18 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * Logs a message with the specific Marker at the {@link Level#INFO INFO} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void info(Marker marker, Message msg); + void info(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#INFO INFO} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void info(Marker marker, Message msg, Throwable t); + void info(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#INFO INFO} level with the @@ -1772,40 +2005,42 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void info(Marker marker, MessageSupplier msgSupplier); + void info(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#INFO INFO} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void info(Marker marker, MessageSupplier msgSupplier, Throwable t); + void info(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#INFO INFO} level. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. + * @since 2.6 */ void info(Marker marker, CharSequence message); /** * Logs a CharSequence at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void info(Marker marker, CharSequence message, Throwable t); + void info(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#INFO INFO} level. @@ -1817,13 +2052,13 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void info(Marker marker, Object message, Throwable t); + void info(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#INFO INFO} level. @@ -1856,71 +2091,71 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void info(Marker marker, String message, Throwable t); + void info(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#INFO INFO} level with the * specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void info(Marker marker, Supplier msgSupplier); + void info(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#INFO INFO} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void info(Marker marker, Supplier msgSupplier, Throwable t); + void info(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#INFO INFO} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void info(Message msg); + void info(Message message); /** * Logs a message with the specific Marker at the {@link Level#INFO INFO} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void info(Message msg, Throwable t); + void info(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#INFO INFO} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void info(MessageSupplier msgSupplier); + void info(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#INFO INFO} level) including the - * stack trace of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack trace of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack trace. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void info(MessageSupplier msgSupplier, Throwable t); + void info(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#INFO INFO} level. @@ -1931,12 +2166,12 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a CharSequence at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void info(CharSequence message, Throwable t); + void info(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#INFO INFO} level. @@ -1947,12 +2182,12 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void info(Object message, Throwable t); + void info(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#INFO INFO} level. @@ -1982,32 +2217,32 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#INFO INFO} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void info(String message, Throwable t); + void info(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#INFO INFO} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void info(Supplier msgSupplier); + void info(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#INFO INFO} level) including the - * stack trace of the {@link Throwable} t passed as parameter. + * stack trace of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void info(Supplier msgSupplier, Throwable t); + void info(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at info level. @@ -2015,6 +2250,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void info(Marker marker, String message, Object p0); @@ -2025,6 +2261,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @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. + * @since 2.6 */ void info(Marker marker, String message, Object p0, Object p1); @@ -2036,6 +2273,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void info(Marker marker, String message, Object p0, Object p1, Object p2); @@ -2048,6 +2286,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -2061,6 +2300,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -2075,6 +2315,7 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -2090,9 +2331,10 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void info( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at info level. @@ -2107,8 +2349,18 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void info( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -2125,9 +2377,20 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void info( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at info level. @@ -2144,15 +2407,28 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void info(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void info( + Marker marker, + 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 with parameters at info level. * * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void info(String message, Object p0); @@ -2162,6 +2438,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @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. + * @since 2.6 */ void info(String message, Object p0, Object p1); @@ -2172,6 +2449,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2); @@ -2183,6 +2461,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2, Object p3); @@ -2195,6 +2474,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -2208,6 +2488,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -2222,6 +2503,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -2237,6 +2519,7 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); @@ -2253,8 +2536,18 @@ void info(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void info( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -2271,9 +2564,20 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + void info( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9); /** * Checks whether this Logger is enabled for the {@link Level#DEBUG DEBUG} Level. @@ -2396,19 +2700,19 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void log(Level level, Marker marker, Message msg); + void log(Level level, Marker marker, Message message); /** * Logs a message with the specific Marker at the given level. * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void log(Level level, Marker marker, Message msg, Throwable t); + void log(Level level, Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the specified level with the specified @@ -2417,23 +2721,23 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void log(Level level, Marker marker, MessageSupplier msgSupplier); + void log(Level level, Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the specified level) with the specified Marker and - * including the stack log of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} + * including the stack log of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} * may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void log(Level level, Marker marker, MessageSupplier msgSupplier, Throwable t); + void log(Level level, Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the given level. @@ -2441,19 +2745,21 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param level the logging level * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. + * @since 2.6 */ void log(Level level, Marker marker, CharSequence message); /** - * Logs a CharSequence at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a CharSequence at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void log(Level level, Marker marker, CharSequence message, Throwable t); + void log(Level level, Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the given level. @@ -2465,15 +2771,15 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, void log(Level level, Marker marker, Object message); /** - * Logs a message at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a message at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param marker the marker data specific to this log statement * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void log(Level level, Marker marker, Object message, Throwable t); + void log(Level level, Marker marker, Object message, Throwable throwable); /** * Logs a message object with the given level. @@ -2507,96 +2813,98 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, void log(Level level, Marker marker, String message, Supplier... paramSuppliers); /** - * Logs a message at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a message at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param marker the marker data specific to this log statement * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void log(Level level, Marker marker, String message, Throwable t); + void log(Level level, Marker marker, String message, Throwable throwable); /** * Logs a message (only to be constructed if the logging level is the specified level) with the specified Marker. * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void log(Level level, Marker marker, Supplier msgSupplier); + void log(Level level, Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the specified level) with the specified Marker and - * including the stack log of the {@link Throwable} t passed as parameter. + * including the stack log of the {@link Throwable} throwable passed as parameter. * * @param level the logging level * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void log(Level level, Marker marker, Supplier msgSupplier, Throwable t); + void log(Level level, Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the given level. * * @param level the logging level - * @param msg the message string to be logged + * @param message the message string to be logged */ - void log(Level level, Message msg); + void log(Level level, Message message); /** * Logs a message with the specific Marker at the given level. * * @param level the logging level - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void log(Level level, Message msg, Throwable t); + void log(Level level, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the specified level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param level the logging level - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void log(Level level, MessageSupplier msgSupplier); + void log(Level level, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the specified level) including the stack log of - * the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may not use the + * the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may not use the * {@link MessageFactory} to construct the {@code Message}. * * @param level the logging level - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack log. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack log. * @since 2.4 */ - void log(Level level, MessageSupplier msgSupplier, Throwable t); + void log(Level level, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the given level. * * @param level the logging level * @param message the message CharSequence to log. + * @since 2.6 */ void log(Level level, CharSequence message); /** - * Logs a CharSequence at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a CharSequence at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void log(Level level, CharSequence message, Throwable t); + void log(Level level, CharSequence message, Throwable throwable); /** * Logs a message object with the given level. @@ -2607,14 +2915,14 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, void log(Level level, Object message); /** - * Logs a message at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a message at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void log(Level level, Object message, Throwable t); + void log(Level level, Object message, Throwable throwable); /** * Logs a message object with the given level. @@ -2645,36 +2953,36 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, void log(Level level, String message, Supplier... paramSuppliers); /** - * Logs a message at the given level including the stack trace of the {@link Throwable} t passed as + * Logs a message at the given level including the stack trace of the {@link Throwable} throwable passed as * parameter. * * @param level the logging level * @param message the message to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void log(Level level, String message, Throwable t); + void log(Level level, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the specified level. * * @param level the logging level - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void log(Level level, Supplier msgSupplier); + void log(Level level, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the specified level) including the stack log of - * the {@link Throwable} t passed as parameter. + * the {@link Throwable} throwable passed as parameter. * * @param level the logging level - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack log. + * @param throwable the {@code Throwable} to log, including its stack log. * @since 2.4 */ - void log(Level level, Supplier msgSupplier, Throwable t); + void log(Level level, Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at the specified level. @@ -2683,6 +2991,7 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void log(Level level, Marker marker, String message, Object p0); @@ -2694,6 +3003,7 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @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. + * @since 2.6 */ void log(Level level, Marker marker, String message, Object p0, Object p1); @@ -2706,6 +3016,7 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2); @@ -2719,6 +3030,7 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -2733,6 +3045,7 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -2748,8 +3061,18 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ - void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); + void log( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5); /** * Logs a message with parameters at the specified level. @@ -2764,8 +3087,18 @@ void info(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, + void log( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, Object p6); /** @@ -2782,8 +3115,19 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void log( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -2801,9 +3145,21 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void log(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void log( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at the specified level. @@ -2821,9 +3177,22 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void log(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); + void log( + 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); /** * Logs a message with parameters at the specified level. @@ -2831,6 +3200,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param level the logging level * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0); @@ -2841,6 +3211,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @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. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1); @@ -2852,6 +3223,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1, Object p2); @@ -2864,6 +3236,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3); @@ -2877,6 +3250,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -2891,6 +3265,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -2906,6 +3281,7 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -2922,8 +3298,19 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); + void log( + Level level, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7); /** * Logs a message with parameters at the specified level. @@ -2939,8 +3326,19 @@ void log(Level level, Marker marker, String message, Object p0, Object p1, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void log( + Level level, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -2958,9 +3356,21 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void log(Level level, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + void log( + Level level, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9); /** * Logs a formatted message using the specified format string and arguments. @@ -2982,7 +3392,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 void printf(Level level, String format, Object... params); /** - * Logs an exception or error to be thrown. This may be coded as: + * Logs a {@link Throwable} to be thrown. This may be coded as: * *
      * throw logger.throwing(Level.DEBUG, myException);
@@ -2990,40 +3400,41 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3
      *
      * @param  the Throwable type.
      * @param level The logging Level.
-     * @param t The Throwable.
+     * @param throwable The Throwable.
      * @return the Throwable.
      */
-     T throwing(Level level, T t);
+     T throwing(Level level, T throwable);
 
     /**
-     * Logs an exception or error to be thrown. This may be coded as:
+     * Logs a {@link Throwable} to be thrown at the {@link Level#ERROR ERROR} level.
+     * This may be coded as:
      *
      * 
      * throw logger.throwing(myException);
      * 
* * @param the Throwable type. - * @param t The Throwable. + * @param throwable The Throwable. * @return the Throwable. */ - T throwing(T t); + T throwing(T throwable); /** * Logs a message with the specific Marker at the {@link Level#TRACE TRACE} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void trace(Marker marker, Message msg); + void trace(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#TRACE TRACE} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void trace(Marker marker, Message msg, Throwable t); + void trace(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#TRACE TRACE} level with @@ -3031,41 +3442,43 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void trace(Marker marker, MessageSupplier msgSupplier); + void trace(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#TRACE TRACE} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void trace(Marker marker, MessageSupplier msgSupplier, Throwable t); + void trace(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#TRACE TRACE} level. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. + * @since 2.6 */ void trace(Marker marker, CharSequence message); /** * Logs a CharSequence at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) + * @since 2.6 */ - void trace(Marker marker, CharSequence message, Throwable t); + void trace(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#TRACE TRACE} level. @@ -3077,14 +3490,14 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 /** * Logs a message at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) */ - void trace(Marker marker, Object message, Throwable t); + void trace(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#TRACE TRACE} level. @@ -3117,72 +3530,72 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 /** * Logs a message at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) */ - void trace(Marker marker, String message, Throwable t); + void trace(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#TRACE TRACE} level with * the specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void trace(Marker marker, Supplier msgSupplier); + void trace(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#TRACE TRACE} level) with the - * specified Marker and including the stack trace of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack trace of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void trace(Marker marker, Supplier msgSupplier, Throwable t); + void trace(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#TRACE TRACE} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void trace(Message msg); + void trace(Message message); /** * Logs a message with the specific Marker at the {@link Level#TRACE TRACE} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void trace(Message msg, Throwable t); + void trace(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#TRACE TRACE} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void trace(MessageSupplier msgSupplier); + void trace(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#TRACE TRACE} level) including the - * stack trace of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack trace of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack trace. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void trace(MessageSupplier msgSupplier, Throwable t); + void trace(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#TRACE TRACE} level. @@ -3193,13 +3606,13 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 /** * Logs a CharSequence at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) */ - void trace(CharSequence message, Throwable t); + void trace(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#TRACE TRACE} level. @@ -3210,13 +3623,13 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 /** * Logs a message at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) */ - void trace(Object message, Throwable t); + void trace(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#TRACE TRACE} level. @@ -3246,33 +3659,33 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 /** * Logs a message at the {@link Level#TRACE TRACE} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @see #debug(String) */ - void trace(String message, Throwable t); + void trace(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#TRACE TRACE} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void trace(Supplier msgSupplier); + void trace(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#TRACE TRACE} level) including the - * stack trace of the {@link Throwable} t passed as parameter. + * stack trace of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. * @since 2.4 */ - void trace(Supplier msgSupplier, Throwable t); + void trace(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at trace level. @@ -3280,6 +3693,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param marker the marker data specific to this log statement * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void trace(Marker marker, String message, Object p0); @@ -3290,6 +3704,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @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. + * @since 2.6 */ void trace(Marker marker, String message, Object p0, Object p1); @@ -3301,6 +3716,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void trace(Marker marker, String message, Object p0, Object p1, Object p2); @@ -3313,6 +3729,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3); @@ -3326,6 +3743,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -3340,6 +3758,7 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -3355,9 +3774,10 @@ void log(Level level, String message, Object p0, Object p1, Object p2, Object p3 * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ - void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void trace( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at trace level. @@ -3372,8 +3792,18 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ - void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void trace( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -3390,9 +3820,20 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void trace( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at trace level. @@ -3409,15 +3850,28 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void trace(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void trace( + Marker marker, + 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 with parameters at trace level. * * @param message the message to log; the format depends on the message factory. * @param p0 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0); @@ -3427,6 +3881,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @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. + * @since 2.6 */ void trace(String message, Object p0, Object p1); @@ -3437,6 +3892,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p0 parameter to the message. * @param p1 parameter to the message. * @param p2 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2); @@ -3448,6 +3904,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p1 parameter to the message. * @param p2 parameter to the message. * @param p3 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2, Object p3); @@ -3460,6 +3917,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p2 parameter to the message. * @param p3 parameter to the message. * @param p4 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4); @@ -3473,6 +3931,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p3 parameter to the message. * @param p4 parameter to the message. * @param p5 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); @@ -3487,6 +3946,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p4 parameter to the message. * @param p5 parameter to the message. * @param p6 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); @@ -3502,6 +3962,7 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p5 parameter to the message. * @param p6 parameter to the message. * @param p7 parameter to the message. + * @since 2.6 */ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7); @@ -3518,8 +3979,18 @@ void trace(Marker marker, String message, Object p0, Object p1, Object p2, Objec * @param p6 parameter to the message. * @param p7 parameter to the message. * @param p8 parameter to the message. + * @since 2.6 */ - void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void trace( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -3536,9 +4007,20 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p7 parameter to the message. * @param p8 parameter to the message. * @param p9 parameter to the message. + * @since 2.6 */ - void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + void trace( + 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 @@ -3578,12 +4060,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, * - *
+     * 
{@code
      * public void doSomething(Request foo) {
      *     LOGGER.traceEntry(()->gson.toJson(foo));
      *     // do something
      * }
-     * 
+ * }
* * @param paramSuppliers The Suppliers for the parameters to the method. * @return built message @@ -3595,12 +4077,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, * - *
+     * 
{@code
      * public void doSomething(String foo, int bar) {
      *     LOGGER.traceEntry("Parameters: {} and {}", ()->gson.toJson(foo), ()-> bar);
      *     // do something
      * }
-     * 
+ * }
* * @param format The format String for the parameters. * @param paramSuppliers The Suppliers for the parameters to the method. @@ -3727,18 +4209,18 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * Logs a message with the specific Marker at the {@link Level#WARN WARN} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged + * @param message the message string to be logged */ - void warn(Marker marker, Message msg); + void warn(Marker marker, Message message); /** * Logs a message with the specific Marker at the {@link Level#WARN WARN} level. * * @param marker the marker data specific to this log statement - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void warn(Marker marker, Message msg, Throwable t); + void warn(Marker marker, Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#WARN WARN} level with the @@ -3746,40 +4228,42 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void warn(Marker marker, MessageSupplier msgSupplier); + void warn(Marker marker, MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#WARN WARN} level) with the - * specified Marker and including the stack warn of the {@link Throwable} t passed as parameter. The + * specified Marker and including the stack warn of the {@link Throwable} throwable passed as parameter. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t A Throwable or null. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable A Throwable or null. * @since 2.4 */ - void warn(Marker marker, MessageSupplier msgSupplier, Throwable t); + void warn(Marker marker, MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#WARN WARN} level. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. + * @since 2.6 */ void warn(Marker marker, CharSequence message); /** * Logs a CharSequence at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void warn(Marker marker, CharSequence message, Throwable t); + void warn(Marker marker, CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#WARN WARN} level. @@ -3791,13 +4275,13 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void warn(Marker marker, Object message, Throwable t); + void warn(Marker marker, Object message, Throwable throwable); /** * Logs a message object with the {@link Level#WARN WARN} level. @@ -3830,87 +4314,89 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param marker the marker data specific to this log statement * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void warn(Marker marker, String message, Throwable t); + void warn(Marker marker, String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#WARN WARN} level with the * specified Marker. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void warn(Marker marker, Supplier msgSupplier); + void warn(Marker marker, Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#WARN WARN} level) with the - * specified Marker and including the stack warn of the {@link Throwable} t passed as parameter. + * specified Marker and including the stack warn of the {@link Throwable} throwable passed as parameter. * * @param marker the marker data specific to this log statement - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t A Throwable or null. + * @param throwable A Throwable or null. * @since 2.4 */ - void warn(Marker marker, Supplier msgSupplier, Throwable t); + void warn(Marker marker, Supplier messageSupplier, Throwable throwable); /** * Logs a message with the specific Marker at the {@link Level#WARN WARN} level. * - * @param msg the message string to be logged + * @param message the message string to be logged */ - void warn(Message msg); + void warn(Message message); /** * Logs a message with the specific Marker at the {@link Level#WARN WARN} level. * - * @param msg the message string to be logged - * @param t A Throwable or null. + * @param message the message string to be logged + * @param throwable A Throwable or null. */ - void warn(Message msg, Throwable t); + void warn(Message message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#WARN WARN} level. The * {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. + * @param messageSupplier A function, which when called, produces the desired log message. * @since 2.4 */ - void warn(MessageSupplier msgSupplier); + void warn(MessageSupplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#WARN WARN} level) including the - * stack warn of the {@link Throwable} t passed as parameter. The {@code MessageSupplier} may or may + * stack warn of the {@link Throwable} throwable passed as parameter. The {@code MessageSupplier} may or may * not use the {@link MessageFactory} to construct the {@code Message}. * - * @param msgSupplier A function, which when called, produces the desired log message. - * @param t the exception to log, including its stack warn. + * @param messageSupplier A function, which when called, produces the desired log message. + * @param throwable the {@code Throwable} to log, including its stack warn. * @since 2.4 */ - void warn(MessageSupplier msgSupplier, Throwable t); + void warn(MessageSupplier messageSupplier, Throwable throwable); /** * Logs a message CharSequence with the {@link Level#WARN WARN} level. * * @param message the message CharSequence to log. + * @since 2.6 */ void warn(CharSequence message); /** * Logs a CharSequence at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message CharSequence to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. + * @since 2.6 */ - void warn(CharSequence message, Throwable t); + void warn(CharSequence message, Throwable throwable); /** * Logs a message object with the {@link Level#WARN WARN} level. @@ -3921,12 +4407,12 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void warn(Object message, Throwable t); + void warn(Object message, Throwable throwable); /** * Logs a message object with the {@link Level#WARN WARN} level. @@ -3956,32 +4442,32 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs a message at the {@link Level#WARN WARN} level including the stack trace of the {@link Throwable} - * t passed as parameter. + * throwable passed as parameter. * * @param message the message object to log. - * @param t the exception to log, including its stack trace. + * @param throwable the {@code Throwable} to log, including its stack trace. */ - void warn(String message, Throwable t); + void warn(String message, Throwable throwable); /** * Logs a message which is only to be constructed if the logging level is the {@link Level#WARN WARN} level. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. * @since 2.4 */ - void warn(Supplier msgSupplier); + void warn(Supplier messageSupplier); /** * Logs a message (only to be constructed if the logging level is the {@link Level#WARN WARN} level) including the - * stack warn of the {@link Throwable} t passed as parameter. + * stack warn of the {@link Throwable} throwable passed as parameter. * - * @param msgSupplier A function, which when called, produces the desired log message; the format depends on the + * @param messageSupplier A function, which when called, produces the desired log message; the format depends on the * message factory. - * @param t the exception to log, including its stack warn. + * @param throwable the {@code Throwable} to log, including its stack warn. * @since 2.4 */ - void warn(Supplier msgSupplier, Throwable t); + void warn(Supplier messageSupplier, Throwable throwable); /** * Logs a message with parameters at warn level. @@ -4065,8 +4551,8 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * @param p5 parameter to the message. * @param p6 parameter to the message. */ - void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, - Object p6); + void warn( + Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); /** * Logs a message with parameters at warn level. @@ -4082,7 +4568,16 @@ void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p6 parameter to the message. * @param p7 parameter to the message. */ - void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, + void warn( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, Object p7); /** @@ -4100,8 +4595,18 @@ void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p7 parameter to the message. * @param p8 parameter to the message. */ - void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); + void warn( + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Logs a message with parameters at warn level. @@ -4119,8 +4624,19 @@ void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p8 parameter to the message. * @param p9 parameter to the message. */ - void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); + void warn( + Marker marker, + 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 with parameters at warn level. @@ -4228,7 +4744,16 @@ void warn(Marker marker, String message, Object p0, Object p1, Object p2, Object * @param p7 parameter to the message. * @param p8 parameter to the message. */ - void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, + void warn( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, Object p8); /** @@ -4246,7 +4771,110 @@ void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, * @param p8 parameter to the message. * @param p9 parameter to the message. */ - void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, - Object p8, Object p9); + 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 {@code Throwable} 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) { + // noop + } + + /** + * 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 Any level (ignoreed here). + * @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/LoggingException.java b/log4j-api/src/main/java/org/apache/logging/log4j/LoggingException.java index 8c433dbf9c6..f057eda66fb 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/LoggingException.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/LoggingException.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/Marker.java b/log4j-api/src/main/java/org/apache/logging/log4j/Marker.java index 784486eef76..9be36461ebb 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/Marker.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/Marker.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j; @@ -29,7 +29,7 @@ public interface Marker extends Serializable { /** * Adds a Marker as a parent to this Marker. - * + * * @param markers The parent markers to add. * @return The current Marker object, thus allowing multiple adds to be concatenated. * @throws IllegalArgumentException if the argument is {@code null} @@ -48,21 +48,21 @@ public interface Marker extends Serializable { /** * Returns the name of this Marker. - * + * * @return The name of the Marker. */ String getName(); /** * Returns a list of parents of this Marker. - * + * * @return The parent Markers or {@code null} if this Marker has no parents. */ Marker[] getParents(); /** * Returns a hash code value based on the name of this marker. Markers are equal if they have the same name. - * + * * @return the computed hash code * @since 2.4 */ @@ -71,14 +71,14 @@ public interface Marker extends Serializable { /** * Indicates whether this Marker has references to any other Markers. - * + * * @return {@code true} if the Marker has parent Markers */ boolean hasParents(); /** * Checks whether this Marker is an instance of the specified Marker. - * + * * @param m The Marker to check. * @return {@code true} if this Marker or one of its ancestors is the specified Marker, {@code false} otherwise. * @throws IllegalArgumentException if the argument is {@code null} @@ -87,7 +87,7 @@ public interface Marker extends Serializable { /** * Checks whether this Marker is an instance of the specified Marker. - * + * * @param name The name of the Marker. * @return {@code true} if this Marker or one of its ancestors matches the specified name, {@code false} otherwise. * @throws IllegalArgumentException if the argument is {@code null} @@ -96,7 +96,7 @@ public interface Marker extends Serializable { /** * Removes the specified Marker as a parent of this Marker. - * + * * @param marker The marker to remove. * @return {@code true} if the marker was removed. * @throws IllegalArgumentException if the argument is {@code null} @@ -105,7 +105,7 @@ public interface Marker extends Serializable { /** * Replaces the set of parent Markers with the provided Markers. - * + * * @param markers The new set of parent Markers or {@code null} to clear the parents. * @return The current Marker object. */ 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..6dc5953a859 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 @@ -1,25 +1,25 @@ /* * 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.logging.log4j; +import com.google.errorprone.annotations.InlineMe; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; - import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilderFormattable; @@ -60,12 +60,7 @@ public static boolean exists(final String key) { * @throws IllegalArgumentException if the argument is {@code null} */ public static Marker getMarker(final String name) { - Marker result = MARKERS.get(name); - if (result == null) { - MARKERS.putIfAbsent(name, new Log4jMarker(name)); - result = MARKERS.get(name); - } - return result; + return MARKERS.computeIfAbsent(name, Log4jMarker::new); } /** @@ -83,7 +78,7 @@ public static Marker getMarker(final String name, final String parent) { if (parentMarker == null) { throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); } - return getMarker(name, parentMarker); + return getMarker(name).addParents(parentMarker); } /** @@ -95,6 +90,9 @@ public static Marker getMarker(final String name, final String parent) { * @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. */ + @InlineMe( + replacement = "MarkerManager.getMarker(name).addParents(parent)", + imports = "org.apache.logging.log4j.MarkerManager") @Deprecated public static Marker getMarker(final String name, final Marker parent) { return getMarker(name).addParents(parent); @@ -231,10 +229,11 @@ public String getName() { @Override public Marker[] getParents() { - if (this.parents == null) { + final Marker[] parentsSnapshot = parents; + if (parentsSnapshot == null) { return null; } - return Arrays.copyOf(this.parents, this.parents.length); + return Arrays.copyOf(parentsSnapshot, parentsSnapshot.length); } @Override @@ -308,8 +307,8 @@ private static boolean checkParent(final Marker parent, final Marker marker) { if (parent == marker) { return true; } - final Marker[] localParents = parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent - .getParents(); + final Marker[] localParents = + parent instanceof Log4jMarker ? ((Log4jMarker) parent).parents : parent.getParents(); if (localParents != null) { final int localParentsLength = localParents.length; if (localParentsLength == 1) { 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..46dba0dd7d8 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j; import java.io.Serializable; @@ -24,25 +23,25 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; - import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.spi.CleanableThreadContextMap; 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.MutableThreadContextStack; 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.ProviderUtil; /** * 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 org.apache.logging.log4j.spi.DefaultThreadContextMap#INHERITABLE_MAP} + * Log4j system property. *

* @see Thread Context Manual */ @@ -55,8 +54,6 @@ private static class EmptyThreadContextStack extends AbstractCollection private static final long serialVersionUID = 1L; - private static final Iterator EMPTY_ITERATOR = new EmptyIterator<>(); - @Override public String pop() { return null; @@ -101,42 +98,45 @@ public int hashCode() { @Override public ContextStack copy() { - return this; + return new MutableThreadContextStack(); } @Override - public T[] toArray(final T[] a) { + public T[] toArray(final T[] ignored) { throw new UnsupportedOperationException(); } @Override - public boolean add(final String e) { + public boolean add(final String ignored) { throw new UnsupportedOperationException(); } @Override - public boolean containsAll(final Collection c) { + public void clear() {} + + @Override + public boolean containsAll(final Collection ignored) { return false; } @Override - public boolean addAll(final Collection c) { + public boolean addAll(final Collection ignored) { throw new UnsupportedOperationException(); } @Override - public boolean removeAll(final Collection c) { + public boolean removeAll(final Collection ignored) { throw new UnsupportedOperationException(); } @Override - public boolean retainAll(final Collection c) { + public boolean retainAll(final Collection ignored) { throw new UnsupportedOperationException(); } @Override public Iterator iterator() { - return EMPTY_ITERATOR; + return Collections.emptyIterator(); } @Override @@ -150,26 +150,34 @@ public ContextStack getImmutableStackOrNull() { } } - /** - * An empty iterator. Since Java 1.7 added the Collections.emptyIterator() method, we have to make do. - * - * @param the type of the empty iterator - */ - private static class EmptyIterator implements Iterator { + private static final class NoOpThreadContextStack extends EmptyThreadContextStack { @Override - public boolean hasNext() { + public boolean add(final String ignored) { return false; } @Override - public E next() { - throw new NoSuchElementException("This is an empty iterator!"); + public boolean addAll(final Collection ignored) { + return false; + } + + @Override + public void push(final String ignored) {} + + @Override + public boolean remove(final Object ignored) { + return false; + } + + @Override + public boolean removeAll(final Collection ignored) { + return false; } @Override - public void remove() { - // no-op + public boolean retainAll(final Collection ignored) { + return false; } } @@ -188,15 +196,12 @@ 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 ThreadContextMap contextMap; private static ReadOnlyThreadContextMap readOnlyContextMap; static { @@ -209,26 +214,20 @@ private ThreadContext() { /** * Consider private, used for testing. + * + * @since 2.20.0 */ - static void init() { + public static void init() { + final PropertiesUtil properties = PropertiesUtil.getProperties(); + contextStack = properties.getBooleanProperty(DISABLE_STACK) || properties.getBooleanProperty(DISABLE_ALL) + ? new NoOpThreadContextStack() + : new DefaultThreadContextStack(); + // TODO: Fix the tests that need to reset the thread context map to use separate instance of the + // provider instead. 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(); - } - if (contextMap instanceof ReadOnlyThreadContextMap) { - readOnlyContextMap = (ReadOnlyThreadContextMap) contextMap; - } else { - readOnlyContextMap = null; - } + contextMap = ProviderUtil.getProvider().getThreadContextMapInstance(); + readOnlyContextMap = + contextMap instanceof ReadOnlyThreadContextMap ? (ReadOnlyThreadContextMap) contextMap : null; } /** @@ -246,6 +245,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. @@ -261,7 +278,7 @@ public static void putAll(final Map m) { } else if (contextMap instanceof DefaultThreadContextMap) { ((DefaultThreadContextMap) contextMap).putAll(m); } else { - for (final Map.Entry entry: m.entrySet()) { + for (final Map.Entry entry : m.entrySet()) { contextMap.put(entry.getKey(), entry.getValue()); } } @@ -365,8 +382,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 * @since 2.8 */ public static ReadOnlyThreadContextMap getThreadContextMap() { @@ -414,7 +429,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..608f0ad21ec --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java @@ -0,0 +1,398 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Arrays; +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.spi.ExtendedLogger; +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.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 Message EMPTY_MESSAGE = new SimpleMessage(""); + private static final String FQCN = DefaultLogBuilder.class.getName(); + private static final Logger LOGGER = StatusLogger.getLogger(); + + private ExtendedLogger logger; + private Level level; + private Marker marker; + private Throwable throwable; + private StackTraceElement location; + private volatile boolean inUse; + private long threadId; + private String fqcn = FQCN; + + public DefaultLogBuilder(final ExtendedLogger logger, final Level level) { + this.logger = logger; + this.level = level; + this.threadId = Thread.currentThread().getId(); + this.inUse = level != null; + } + + public DefaultLogBuilder() { + this(null, null); + } + + @Override + public void setEntryPoint(final 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 ExtendedLogger logger, final Level level) { + this.logger = logger; + this.level = level; + this.marker = null; + this.throwable = null; + this.location = null; + this.inUse = true; + return this; + } + + @Override + public LogBuilder withMarker(final Marker marker) { + this.marker = marker; + return this; + } + + @Override + public LogBuilder withThrowable(final Throwable throwable) { + this.throwable = throwable; + return this; + } + + @Override + public LogBuilder withLocation() { + location = StackLocatorUtil.getStackTraceElement(2); + return this; + } + + @Override + public LogBuilder withLocation(StackTraceElement location) { + this.location = location; + return this; + } + + public boolean isInUse() { + return inUse; + } + + @Override + public void log(Message message) { + if (isValid() && isEnabled(message)) { + logMessage(message); + } + } + + @Override + @SuppressWarnings("deprecation") + public Message logAndGet(final Supplier messageSupplier) { + Message message = null; + if (isValid() && isEnabled(message = messageSupplier.get())) { + logMessage(message); + } + return message; + } + + @Override + public void log(final CharSequence message) { + if (isValid() && isEnabled(message)) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message) { + if (isValid() && isEnabled(message)) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message, Object... params) { + if (isValid() && isEnabled(message, params)) { + logMessage(logger.getMessageFactory().newMessage(message, params)); + } + } + + @Override + @SuppressWarnings("deprecation") + public void log(String message, Supplier... params) { + final Object[] objs; + if (isValid() && isEnabled(message, objs = LambdaUtil.getAll(params))) { + logMessage(logger.getMessageFactory().newMessage(message, objs)); + } + } + + @Override + @SuppressWarnings("deprecation") + public void log(final Supplier messageSupplier) { + logAndGet(messageSupplier); + } + + @Override + public void log(Object message) { + if (isValid() && isEnabled(message)) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message, Object p0) { + if (isValid() && isEnabled(message, p0)) { + logMessage(logger.getMessageFactory().newMessage(message, p0)); + } + } + + @Override + public void log(String message, Object p0, Object p1) { + if (isValid() && isEnabled(message, p0, p1)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2) { + if (isValid() && isEnabled(message, p0, p1, p2)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3) { + if (isValid() && isEnabled(message, p0, p1, p2, p3)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); + } + } + + @Override + public void log( + String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); + } + } + + @Override + public void log( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); + } + } + + @Override + public void log( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9) { + if (isValid() && isEnabled(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); + } + } + + @Override + public void log() { + if (isValid() && isEnabled(EMPTY_MESSAGE)) { + logMessage(EMPTY_MESSAGE); + } + } + + private void logMessage(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; + } + + protected boolean isEnabled(Message message) { + return logger.isEnabled(level, marker, message, throwable); + } + + protected boolean isEnabled(CharSequence message) { + return logger.isEnabled(level, marker, message, throwable); + } + + protected boolean isEnabled(String message) { + return logger.isEnabled(level, marker, message, throwable); + } + + protected boolean isEnabled(String message, Object... params) { + final Object[] newParams; + if (throwable != null) { + newParams = Arrays.copyOf(params, params.length + 1); + newParams[params.length] = throwable; + } else { + newParams = params; + } + return logger.isEnabled(level, marker, message, newParams); + } + + protected boolean isEnabled(Object message) { + return logger.isEnabled(level, marker, message, throwable); + } + + protected boolean isEnabled(String message, Object p0) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, throwable) + : logger.isEnabled(level, marker, message, p0); + } + + protected boolean isEnabled(String message, Object p0, Object p1) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, throwable) + : logger.isEnabled(level, marker, message, p0, p1); + } + + protected boolean isEnabled(String message, Object p0, Object p1, Object p2) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2); + } + + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3); + } + + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4); + } + + protected boolean isEnabled(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5); + } + + protected boolean isEnabled( + String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6); + } + + protected boolean isEnabled( + String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + protected boolean isEnabled( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + protected boolean isEnabled( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9) { + return throwable != null + ? logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, throwable) + : logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } +} 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..68bd7345833 --- /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/internal/map/UnmodifiableArrayBackedMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/map/UnmodifiableArrayBackedMap.java new file mode 100644 index 00000000000..2911710bd3c --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/map/UnmodifiableArrayBackedMap.java @@ -0,0 +1,526 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.map; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.TriConsumer; + +/** + * This class represents an immutable map, which stores its state inside a single Object[]: + *
    + *
  1. [0] contains the number of entries
  2. + *
  3. Others contain alternating key-value pairs, for example [1]="1" and [2]="value_for_1"
  4. + *
+ * + * Keys are calculated using (index * 2 + 1) and values are (index * 2 + 2). + * + * Performance: + *
    + *
  • Implements very low-cost copies: shallow-copy the array.
  • + *
  • Doesn't matter for mutable operations, since we don't allow them.
  • + *
  • Iterates very quickly, since it iterates directly across the array. This + * contrasts with HashMap's requirement to scan each bucket in the table and + * chase each pointer.
  • + *
  • Is linear on gets, puts, and removes, since the table must be scanned to + * find a matching key.
  • + *
+ * + * Allocation: + *
    + *
  • Zero on reads.
  • + *
  • Copy-and-modify operations allocate exactly two objects: the new array + * and the new Map instance. This is substantially better than HashMap, which + * requires a new Node for each entry.
  • + *
+ * + */ +public class UnmodifiableArrayBackedMap extends AbstractMap implements ReadOnlyStringMap { + /** + * Implementation of Map.Entry. The implementation is simple since each instance + * contains an index in the array, then getKey() and getValue() retrieve from + * the array. Blocks modifications. + */ + private class UnmodifiableEntry implements Map.Entry { + /** + * This field is functionally final, but marking it as such can cause + * performance problems. Consider marking it final after + * https://bugs.openjdk.org/browse/JDK-8324186 is solved. + */ + private int index; + + public UnmodifiableEntry(int index) { + this.index = index; + } + + @Override + public String getKey() { + return (String) backingArray[getArrayIndexForKey(index)]; + } + + @Override + public String getValue() { + return (String) backingArray[getArrayIndexForValue(index)]; + } + + /** + * Per spec, the hashcode is a function of the key and value. Calculation + * exactly matches HashMap. + */ + public int hashCode() { + String key = (String) backingArray[getArrayIndexForKey(index)]; + String value = (String) backingArray[getArrayIndexForValue(index)]; + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException("Cannot update Entry instances in UnmodifiableArrayBackedMap"); + } + } + + /** + * Simple Entry iterator, tracking solely the index in the array. Blocks + * modifications. + */ + private class UnmodifiableEntryIterator implements Iterator> { + private int index; + + @Override + public boolean hasNext() { + return index < numEntries; + } + + @Override + public Entry next() { + return new UnmodifiableEntry(index++); + } + } + + /** + * Simple Entry set, providing a reference to UnmodifiableEntryIterator and + * blocking modifications. + */ + private class UnmodifiableEntrySet extends AbstractSet> { + + @Override + public boolean add(Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> iterator() { + return new UnmodifiableEntryIterator(); + } + + @Override + public int size() { + return numEntries; + } + } + + private static final long serialVersionUID = 6849423432534211514L; + + public static final UnmodifiableArrayBackedMap EMPTY_MAP = new UnmodifiableArrayBackedMap(0); + + private static final int NUM_FIXED_ARRAY_ENTRIES = 1; + + private static int getArrayIndexForKey(int entryIndex) { + return 2 * entryIndex + NUM_FIXED_ARRAY_ENTRIES; + } + + private static int getArrayIndexForValue(int entryIndex) { + return 2 * entryIndex + 1 + NUM_FIXED_ARRAY_ENTRIES; + } + + public static UnmodifiableArrayBackedMap getMap(Object[] backingArray) { + if (backingArray == null || backingArray.length == 1) { + return EMPTY_MAP; + } else { + return new UnmodifiableArrayBackedMap(backingArray); + } + } + + /** + * backingArray is functionally final, but marking it as such can cause + * performance problems. Consider marking it final after + * https://bugs.openjdk.org/browse/JDK-8324186 is solved. + */ + private Object[] backingArray; + + private int numEntries; + + private UnmodifiableArrayBackedMap(int capacity) { + this.backingArray = new Object[capacity * 2 + 1]; + this.backingArray[0] = 0; + } + + private UnmodifiableArrayBackedMap(Object[] backingArray) { + this.numEntries = (backingArray == null ? 0 : (int) backingArray[0]); + this.backingArray = backingArray; + } + + UnmodifiableArrayBackedMap(UnmodifiableArrayBackedMap other) { + this.backingArray = other.backingArray; + this.numEntries = other.numEntries; + } + + private void add(String key, String value) { + backingArray[getArrayIndexForKey(numEntries)] = key; + backingArray[getArrayIndexForValue(numEntries)] = value; + numEntries++; + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Instance cannot be cleared, reuse EMPTY_MAP instead."); + } + + /** + * Scans the array to find a matching key. Linear performance. + */ + @Override + public boolean containsKey(Object key) { + return containsKey((String) key); + } + + @Override + public boolean containsKey(String key) { + int hashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (backingArray[getArrayIndexForKey(i)].hashCode() == hashCode + && backingArray[getArrayIndexForKey(i)].equals(key)) { + return true; + } + } + + return false; + } + + public Object[] getBackingArray() { + return backingArray; + } + + /** + * Scans the array to find a matching value, with linear time. Allows null + * parameter. + */ + @Override + public boolean containsValue(Object value) { + for (int i = 0; i < numEntries; i++) { + Object valueInMap = backingArray[getArrayIndexForValue(i)]; + if (value == null) { + if (valueInMap == null) { + return true; + } + } else if (value.equals(valueInMap)) { + return true; + } + } + return false; + } + + /** + * Creates a new instance that contains the same entries as this map, plus + * either the new entry or updated value passed in the parameters. + * + * @param key + * @param value + * @return + */ + public UnmodifiableArrayBackedMap copyAndPut(String key, String value) { + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + 1); + // include the numEntries value (array index 0) + if (this.numEntries > 0) { + System.arraycopy(this.backingArray, 1, newMap.backingArray, 1, numEntries * 2); + newMap.numEntries = numEntries; + } + newMap.addOrOverwriteKey(key, value); + newMap.updateNumEntriesInArray(); + return newMap; + } + + /** + * Creates a new instance that contains the same entries as this map, plus the + * new entries or updated values passed in the parameters. + */ + public UnmodifiableArrayBackedMap copyAndPutAll(Map entriesToAdd) { + // create a new array that can hold the maximum output size + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries + entriesToAdd.size()); + + // copy the contents of the current map (if any) + if (numEntries > 0) { + System.arraycopy(backingArray, 0, newMap.backingArray, 0, numEntries * 2 + 1); + newMap.numEntries = numEntries; + } + + for (Map.Entry entry : entriesToAdd.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (!this.isEmpty()) { + // The unique elements passed in may overlap the unique elements here - must + // check + newMap.addOrOverwriteKey(key, value); + } else { + // There is no chance of overlapping keys, we can simply add + newMap.add(key, value); + } + } + + newMap.updateNumEntriesInArray(); + return newMap; + } + + /** + * Creates a new instance that contains the same entries as this map, minus the + * entry with the specified key (if such an entry exists). + */ + public UnmodifiableArrayBackedMap copyAndRemove(String key) { + int indexToRemove = -1; + for (int oldIndex = numEntries - 1; oldIndex >= 0; oldIndex--) { + if (backingArray[getArrayIndexForKey(oldIndex)].hashCode() == key.hashCode() + && backingArray[getArrayIndexForKey(oldIndex)].equals(key)) { + indexToRemove = oldIndex; + break; + } + } + + if (indexToRemove == -1) { + // key not found, no change necessary + return this; + } else if (numEntries == 1) { + // we have 1 item and we're about to remove it + return EMPTY_MAP; + } + UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(numEntries); + if (indexToRemove > 0) { + // copy entries before the removed one + System.arraycopy(backingArray, 1, newMap.backingArray, 1, indexToRemove * 2); + } + if (indexToRemove + 1 < numEntries) { + // copy entries after the removed one + int nextIndexToCopy = indexToRemove + 1; + int numRemainingEntries = numEntries - nextIndexToCopy; + System.arraycopy( + backingArray, + getArrayIndexForKey(nextIndexToCopy), + newMap.backingArray, + getArrayIndexForKey(indexToRemove), + numRemainingEntries * 2); + } + + newMap.numEntries = numEntries - 1; + newMap.updateNumEntriesInArray(); + return newMap; + } + + /** + * Creates a new instance where the entries of provided keys are removed. + */ + public UnmodifiableArrayBackedMap copyAndRemoveAll(Iterable keysToRemoveIterable) { + + // Short-circuit if the map is empty + if (isEmpty()) { + return EMPTY_MAP; + } + + // Collect distinct keys to remove + final Set keysToRemove; + if (keysToRemoveIterable instanceof Set) { + keysToRemove = (Set) keysToRemoveIterable; + } else { + keysToRemove = new HashSet<>(); + for (final String key : keysToRemoveIterable) { + keysToRemove.add(key); + } + } + + // Create the new map + final UnmodifiableArrayBackedMap oldMap = this; + final int oldMapEntryCount = oldMap.numEntries; + final UnmodifiableArrayBackedMap newMap = new UnmodifiableArrayBackedMap(oldMapEntryCount); + + // Short-circuit if there is nothing to remove + if (keysToRemove.isEmpty()) { + System.arraycopy(oldMap.backingArray, 0, newMap.backingArray, 0, oldMapEntryCount * 2); + newMap.numEntries = oldMapEntryCount; + return this; + } + + // Iterate over old map entries + int newMapEntryIndex = 0; + for (int oldMapEntryIndex = 0; oldMapEntryIndex < oldMapEntryCount; oldMapEntryIndex++) { + final int oldMapKeyIndex = getArrayIndexForKey(oldMapEntryIndex); + final Object key = oldMap.backingArray[oldMapKeyIndex]; + + // Skip entries of removed keys + @SuppressWarnings("SuspiciousMethodCalls") + final boolean removed = keysToRemove.contains(key); + if (removed) { + continue; + } + + // Copy the entry + final int oldMapValueIndex = getArrayIndexForValue(oldMapEntryIndex); + final Object value = oldMap.backingArray[oldMapValueIndex]; + final int newMapKeyIndex = getArrayIndexForKey(newMapEntryIndex); + final int newMapValueIndex = getArrayIndexForValue(newMapEntryIndex); + newMap.backingArray[newMapKeyIndex] = key; + newMap.backingArray[newMapValueIndex] = value; + newMapEntryIndex++; + } + + // Cap and return the new map + newMap.numEntries = newMapEntryIndex; + newMap.updateNumEntriesInArray(); + return newMap; + } + + /** + * Copies the locally-tracked numEntries into the first array slot. Requires + * autoboxing so call should be minimized - for example, once per bulk update + * operation. + */ + private void updateNumEntriesInArray() { + backingArray[0] = numEntries; + } + + /** + * This version of forEach is defined on the Map interface. + */ + @Override + public void forEach(java.util.function.BiConsumer action) { + for (int i = 0; i < numEntries; i++) { + // BiConsumer should be able to handle values of any type V. In our case the values are of type String. + final String key = (String) backingArray[getArrayIndexForKey(i)]; + final String value = (String) backingArray[getArrayIndexForValue(i)]; + action.accept(key, value); + } + } + + /** + * This version of forEach is defined on the ReadOnlyStringMap interface. + */ + @SuppressWarnings("unchecked") + @Override + public void forEach(final org.apache.logging.log4j.util.BiConsumer action) { + for (int i = 0; i < numEntries; i++) { + // BiConsumer should be able to handle values of any type V. In our case the values are of type String. + final String key = (String) backingArray[getArrayIndexForKey(i)]; + final V value = (V) backingArray[getArrayIndexForValue(i)]; + action.accept(key, value); + } + } + + @SuppressWarnings("unchecked") + public void forEach(final TriConsumer action, final S state) { + for (int i = 0; i < numEntries; i++) { + // TriConsumer should be able to handle values of any type V. In our case the values are of type String. + final String key = (String) backingArray[getArrayIndexForKey(i)]; + final V value = (V) backingArray[getArrayIndexForValue(i)]; + action.accept(key, value, state); + } + } + + @Override + public Set> entrySet() { + return new UnmodifiableEntrySet(); + } + + /** + * Scans the array to find a matching key. Linear-time. + */ + @Override + public String get(Object key) { + return getValue((String) key); + } + + @SuppressWarnings("unchecked") + @Override + public V getValue(String key) { + if (numEntries == 0) { + return null; + } + int hashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (backingArray[getArrayIndexForKey(i)].hashCode() == hashCode + && backingArray[getArrayIndexForKey(i)].equals(key)) { + return (V) backingArray[getArrayIndexForValue(i)]; + } + } + return null; + } + + /** + * Find an existing entry (if any) and overwrites the value, if found + * + * @param key + * @param value + * @return + */ + private void addOrOverwriteKey(String key, String value) { + int keyHashCode = key.hashCode(); + for (int i = 0; i < numEntries; i++) { + if (backingArray[getArrayIndexForKey(i)].hashCode() == keyHashCode + && backingArray[getArrayIndexForKey(i)].equals(key)) { + // found a match, overwrite then return + backingArray[getArrayIndexForValue(i)] = value; + return; + } + } + + // no match found, add to the end + add(key, value); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException("put() is not supported, use copyAndPut instead"); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("putAll() is not supported, use copyAndPutAll instead"); + } + + @Override + public String remove(Object key) { + throw new UnsupportedOperationException("remove() is not supported, use copyAndRemove instead"); + } + + @Override + public int size() { + return numEntries; + } + + @Override + public Map toMap() { + return this; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/package-info.java new file mode 100644 index 00000000000..cc9a920b43a --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/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 should be considered private. + */ +package org.apache.logging.log4j.internal; 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..c39c65becce 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -24,7 +24,9 @@ *

* This class is immutable. *

- *

Note to implementors

+ *

+ * 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 @@ -64,7 +66,7 @@ public Message newMessage(final String message) { */ @Override public Message newMessage(final String message, final Object p0) { - return newMessage(message, new Object[] { p0 }); + return newMessage(message, new Object[] {p0}); } /** @@ -72,7 +74,7 @@ public Message newMessage(final String message, final Object p0) { */ @Override public Message newMessage(final String message, final Object p0, final Object p1) { - return newMessage(message, new Object[] { p0, p1 }); + return newMessage(message, new Object[] {p0, p1}); } /** @@ -80,67 +82,109 @@ public Message newMessage(final String message, final Object p0, final Object p1 */ @Override public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { - return newMessage(message, new Object[] { p0, p1, 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 }); + 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 }); + 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 }); + 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, + 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 }); + 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 }); + 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 }); + 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 }); + 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/AsynchronouslyFormattable.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/AsynchronouslyFormattable.java index 89526c83ed7..6ed2684fc97 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/AsynchronouslyFormattable.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/AsynchronouslyFormattable.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -55,6 +55,5 @@ */ @Documented // This annotation is part of the public API of annotated elements. @Target(ElementType.TYPE) // Only applies to types. -@Retention(RetentionPolicy.RUNTIME) //Needs to be reflectively discoverable runtime. -public @interface AsynchronouslyFormattable { -} +@Retention(RetentionPolicy.RUNTIME) // Needs to be reflectively discoverable runtime. +public @interface AsynchronouslyFormattable {} 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..fae8236793f 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 @@ -1,22 +1,24 @@ /* * 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.logging.log4j.message; -import org.apache.logging.log4j.util.Chars; +import static org.apache.logging.log4j.util.Chars.LF; +import static org.apache.logging.log4j.util.Chars.SPACE; + import org.apache.logging.log4j.util.StringBuilders; /** @@ -27,10 +29,8 @@ class BasicThreadInformation implements ThreadInformation { private static final int HASH_MULTIPLIER = 31; private final long id; private final String name; - private final String longName; private final Thread.State state; private final int priority; - private final boolean isAlive; private final boolean isDaemon; private final String threadGroupName; @@ -41,10 +41,10 @@ class BasicThreadInformation implements ThreadInformation { BasicThreadInformation(final Thread thread) { this.id = thread.getId(); this.name = thread.getName(); - this.longName = thread.toString(); + this.state = thread.getState(); this.priority = thread.getPriority(); - this.isAlive = thread.isAlive(); + this.isDaemon = thread.isDaemon(); final ThreadGroup group = thread.getThreadGroup(); threadGroupName = group == null ? null : group.getName(); @@ -55,7 +55,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof BasicThreadInformation)) { return false; } @@ -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 @@ -84,7 +80,7 @@ public int hashCode() { */ @Override public void printThreadInfo(final StringBuilder sb) { - StringBuilders.appendDqValue(sb, name).append(Chars.SPACE); + StringBuilders.appendDqValue(sb, name).append(SPACE); if (isDaemon) { sb.append("daemon "); } @@ -92,8 +88,8 @@ public void printThreadInfo(final StringBuilder sb) { if (threadGroupName != null) { StringBuilders.appendKeyDqValue(sb, "group", threadGroupName); } - sb.append('\n'); - sb.append("\tThread state: ").append(state.name()).append('\n'); + sb.append(LF); + sb.append("\tThread state: ").append(state.name()).append(LF); } /** @@ -104,7 +100,7 @@ public void printThreadInfo(final StringBuilder sb) { @Override public void printStack(final StringBuilder sb, final StackTraceElement[] trace) { for (final StackTraceElement element : trace) { - sb.append("\tat ").append(element).append('\n'); + sb.append("\tat ").append(element).append(LF); } } } 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..d17367f903b --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/Clearable.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.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..7b93274578e 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 @@ -1,22 +1,25 @@ /* * 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.logging.log4j.message; import java.io.Serializable; +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. @@ -29,6 +32,11 @@ public class DefaultFlowMessageFactory implements FlowMessageFactory, Serializab private static final String ENTRY_DEFAULT_PREFIX = "Enter"; private static final long serialVersionUID = 8578655591131397576L; + /** + * @since 2.24.0 + */ + public static final FlowMessageFactory INSTANCE = new DefaultFlowMessageFactory(); + private final String entryText; private final String exitText; @@ -45,12 +53,11 @@ public DefaultFlowMessageFactory() { * @param exitText the text to use for trace exit, like {@code "Exit"}. */ public DefaultFlowMessageFactory(final String entryText, final String exitText) { - super(); this.entryText = entryText; this.exitText = exitText; } - 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(final StringBuilder buffer) { + buffer.append(text); + if (message != null) { + buffer.append(" "); + StringBuilders.appendValue(buffer, message); + } + } } private static final class SimpleEntryMessage extends AbstractFlowMessage implements EntryMessage { @@ -111,7 +127,6 @@ private static final class SimpleEntryMessage extends AbstractFlowMessage implem SimpleEntryMessage(final String entryText, final Message message) { super(entryText, message); } - } private static final class SimpleExitMessage extends AbstractFlowMessage implements ExitMessage { @@ -122,15 +137,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 +182,28 @@ public String getExitText() { return exitText; } + @Override + public EntryMessage newEntryMessage(final String format, final Object... params) { + final boolean hasFormat = Strings.isNotEmpty(format); + final Message message; + if (params == null || params.length == 0) { + message = hasFormat ? ParameterizedMessageFactory.INSTANCE.newMessage(format) : null; + } else if (hasFormat) { + message = ParameterizedMessageFactory.INSTANCE.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 = ParameterizedMessageFactory.INSTANCE.newMessage(sb.toString(), params); + } + return newEntryMessage(message); + } + /* * (non-Javadoc) * @@ -176,10 +215,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(final String format, final Object result) { + final boolean hasFormat = Strings.isNotEmpty(format); + final Message message; + if (result == null) { + message = hasFormat ? ParameterizedMessageFactory.INSTANCE.newMessage(format) : null; + } else { + message = ParameterizedMessageFactory.INSTANCE.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/EntryMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/EntryMessage.java index c00bd4c2e19..b7aae3feb9b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/EntryMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/EntryMessage.java @@ -1,26 +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.message; - -/** - * Entry flow messages - * - * @since 2.6 - */ -public interface EntryMessage extends FlowMessage { - // empty -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * Entry flow messages + * + * @since 2.6 + */ +public interface EntryMessage extends FlowMessage { + // empty +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ExitMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ExitMessage.java index 48722f6485b..e15d0a9cdd8 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ExitMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ExitMessage.java @@ -1,26 +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.message; - -/** - * Exit flow messages - * - * @since 2.6 - */ -public interface ExitMessage extends FlowMessage { - // empty -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * Exit flow messages + * + * @since 2.6 + */ +public interface ExitMessage extends FlowMessage { + // empty +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessage.java index c6d180d6227..27098424bec 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessage.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.message; - -/** - * Flow messages - * - * @since 2.6 - */ -public interface FlowMessage extends Message { - - /** - * The message text like "Enter" or "Exit" used to prefix the actual Message. - * - * @return message text used to prefix the actual Message. - */ - String getText(); - - /** - * The wrapped message - * - * @return the wrapped message - */ - Message getMessage(); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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; + +/** + * Flow messages + * + * @since 2.6 + */ +public interface FlowMessage extends Message { + + /** + * The message text like "Enter" or "Exit" used to prefix the actual Message. + * + * @return message text used to prefix the actual Message. + */ + String getText(); + + /** + * The wrapped message + * + * @return the wrapped message + */ + Message getMessage(); +} 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..d53dc872d46 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -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.0 + */ + 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.0 + */ + 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.0 + */ + 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..31c4040251d 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -23,7 +23,6 @@ import java.text.MessageFormat; import java.util.Arrays; import java.util.Locale; -import java.util.regex.Pattern; /** * Handles messages that contain a format String. Dynamically determines if the format conforms to @@ -33,8 +32,6 @@ public class FormattedMessage implements Message { private static final long serialVersionUID = -665975803997290697L; private static final int HASHVAL = 31; - private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; - private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER); private String messagePattern; private transient Object[] argArray; @@ -43,7 +40,7 @@ public class FormattedMessage implements Message { private final Throwable throwable; private Message message; private final Locale locale; - + /** * Constructs with a locale, a pattern and a single parameter. * @param locale The locale @@ -52,7 +49,7 @@ public class FormattedMessage implements Message { * @since 2.6 */ public FormattedMessage(final Locale locale, final String messagePattern, final Object arg) { - this(locale, messagePattern, new Object[] { arg }, null); + this(locale, messagePattern, new Object[] {arg}, null); } /** @@ -64,7 +61,7 @@ public FormattedMessage(final Locale locale, final String messagePattern, final * @since 2.6 */ public FormattedMessage(final Locale locale, final String messagePattern, final Object arg1, final Object arg2) { - this(locale, messagePattern, new Object[] { arg1, arg2 }); + this(locale, messagePattern, new Object[] {arg1, arg2}); } /** @@ -86,7 +83,8 @@ public FormattedMessage(final Locale locale, final String messagePattern, final * @param throwable The throwable * @since 2.6 */ - public FormattedMessage(final Locale locale, final String messagePattern, final Object[] arguments, final Throwable throwable) { + public FormattedMessage( + final Locale locale, final String messagePattern, final Object[] arguments, final Throwable throwable) { this.locale = locale; this.messagePattern = messagePattern; this.argArray = arguments; @@ -99,7 +97,7 @@ public FormattedMessage(final Locale locale, final String messagePattern, final * @param arg The parameter. */ public FormattedMessage(final String messagePattern, final Object arg) { - this(messagePattern, new Object[] { arg }, null); + this(messagePattern, new Object[] {arg}, null); } /** @@ -109,7 +107,7 @@ public FormattedMessage(final String messagePattern, final Object arg) { * @param arg2 The second parameter. */ public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) { - this(messagePattern, new Object[] { arg1, arg2 }); + this(messagePattern, new Object[] {arg1, arg2}); } /** @@ -134,13 +132,12 @@ public FormattedMessage(final String messagePattern, final Object[] arguments, f this.throwable = throwable; } - @Override public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof FormattedMessage)) { return false; } @@ -149,11 +146,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); } /** @@ -180,24 +173,43 @@ public String getFormattedMessage() { return formattedMessage; } + /** + * Gets the message implementation to which formatting is delegated. + * + *

    + *
  • if {@code msgPattern} contains {@link MessageFormat} format specifiers a {@link MessageFormatMessage} + * is returned,
  • + *
  • if {@code msgPattern} contains {@code {}} placeholders a {@link ParameterizedMessage} is returned,
  • + *
  • if {@code msgPattern} contains {@link Format} specifiers a {@link StringFormattedMessage} is returned + * .
  • + *
+ *

+ * Mixing specifiers from multiple types is not supported. + *

+ * + * @param msgPattern The message pattern. + * @param args The parameters. + * @param aThrowable The throwable + * @return The message that performs formatting. + */ protected Message getMessage(final String msgPattern, final Object[] args, final Throwable aThrowable) { + // Check for valid `{ ArgumentIndex [, FormatType [, FormatStyle]] }` format specifiers try { final MessageFormat format = new MessageFormat(msgPattern); final Format[] formats = format.getFormats(); - if (formats != null && formats.length > 0) { + if (formats.length > 0) { return new MessageFormatMessage(locale, msgPattern, args); } } catch (final Exception ignored) { // Obviously, the message is not a proper pattern for MessageFormat. } - try { - if (MSG_PATTERN.matcher(msgPattern).find()) { - return new StringFormattedMessage(locale, msgPattern, args); - } - } catch (final Exception ignored) { - // Also not properly formatted. + // Check for non-escaped `{}` format specifiers + // This case also includes patterns without any `java.util.Formatter` specifiers + if (ParameterFormatter.analyzePattern(msgPattern, 1).placeholderCount > 0 || msgPattern.indexOf('%') == -1) { + return new ParameterizedMessage(msgPattern, args, aThrowable); } - return new ParameterizedMessage(msgPattern, args, aThrowable); + // Interpret as `java.util.Formatter` format + return new StringFormattedMessage(locale, msgPattern, args); } /** @@ -223,7 +235,6 @@ public Throwable getThrowable() { return message.getThrowable(); } - @Override public int hashCode() { int result = messagePattern != null ? messagePattern.hashCode() : 0; 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..566c2b90162 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 @@ -1,26 +1,28 @@ /* * 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.logging.log4j.message; /** * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by * extension.) - * - *

Note to implementors

+ * + *

+ * Note to implementors: + *

*

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

@@ -32,9 +34,7 @@ public class FormattedMessageFactory extends AbstractMessageFactory { /** * Constructs a message factory with default flow strings. */ - public FormattedMessageFactory() { - super(); - } + public FormattedMessageFactory() {} /** * Creates {@link StringFormattedMessage} instances. @@ -78,7 +78,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @since 2.6.1 */ @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new FormattedMessage(message, p0, p1, p2, p3); } @@ -86,7 +87,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new FormattedMessage(message, p0, p1, p2, p3, p4); } @@ -94,7 +96,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new FormattedMessage(message, p0, p1, p2, p3, p4, p5); } @@ -102,7 +111,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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, + 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 new FormattedMessage(message, p0, p1, p2, p3, p4, p5, p6); } @@ -111,8 +127,16 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new FormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } @@ -120,8 +144,17 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new FormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @@ -129,8 +162,18 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new FormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } } 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..c3152224d93 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -22,7 +22,6 @@ import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; - import org.apache.logging.log4j.status.StatusLogger; /** @@ -80,8 +79,8 @@ public LocalizedMessage(final String baseName, final Locale locale, final String this.locale = locale; } - public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, - final Object[] arguments) { + public LocalizedMessage( + final ResourceBundle bundle, final Locale locale, final String key, final Object[] arguments) { this.key = key; this.argArray = arguments; this.throwable = null; @@ -137,13 +136,13 @@ public LocalizedMessage(final ResourceBundle bundle, final String key, final Obj this(bundle, (Locale) null, key, new Object[] {arg1, arg2}); } - public LocalizedMessage(final String baseName, final Locale locale, final String key, final Object arg1, - final Object arg2) { + public LocalizedMessage( + final String baseName, final Locale locale, final String key, final Object arg1, final Object arg2) { this(baseName, locale, key, new Object[] {arg1, arg2}); } - public LocalizedMessage(final ResourceBundle bundle, final Locale locale, final String key, final Object arg1, - final Object arg2) { + public LocalizedMessage( + final ResourceBundle bundle, final Locale locale, final String key, final Object arg1, final Object arg2) { this(bundle, locale, key, new Object[] {arg1, arg2}); } @@ -153,7 +152,7 @@ public LocalizedMessage(final Locale locale, final String key, final Object arg1 /** * Set the name of the Logger. - * + * * @param name The name of the Logger. */ @Override @@ -163,7 +162,7 @@ public void setLoggerName(final String name) { /** * Returns the name of the Logger. - * + * * @return the name of the Logger. */ @Override @@ -173,7 +172,7 @@ public String getLoggerName() { /** * Returns the formatted message after looking up the format in the resource bundle. - * + * * @return The formatted message String. */ @Override @@ -218,15 +217,15 @@ public Throwable getThrowable() { /** * Override this to use a ResourceBundle.Control in Java 6 - * + * * @param rbBaseName The base name of the resource bundle, a fully qualified class name. * @param resourceBundleLocale The locale to use when formatting the message. * @param loop If true the key will be treated as a package or class name and a resource bundle will be located * based on all or part of the package name. If false the key is expected to be the exact bundle id. * @return The ResourceBundle. */ - protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale resourceBundleLocale, - final boolean loop) { + protected ResourceBundle getResourceBundle( + final String rbBaseName, final Locale resourceBundleLocale, final boolean loop) { ResourceBundle rb = null; if (rbBaseName == null) { @@ -240,7 +239,7 @@ protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale } } catch (final MissingResourceException ex) { if (!loop) { - logger.debug("Unable to locate ResourceBundle " + rbBaseName); + logger.debug("Unable to locate ResourceBundle {}", rbBaseName); return null; } } @@ -256,7 +255,7 @@ protected ResourceBundle getResourceBundle(final String rbBaseName, final Locale rb = ResourceBundle.getBundle(substr); } } catch (final MissingResourceException ex) { - logger.debug("Unable to locate ResourceBundle " + substr); + logger.debug("Unable to locate ResourceBundle {}", substr); } } return rb; 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..10d75fc9072 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 @@ -1,28 +1,32 @@ /* * 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.logging.log4j.message; +import java.util.Objects; import java.util.ResourceBundle; +import org.jspecify.annotations.Nullable; /** * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by * extension.) - * - *

Note to implementors

+ * + *

+ * Note to implementors: + *

*

* This class does not implement any {@link MessageFactory2} methods and lets the superclass funnel those calls * through {@link #newMessage(String, Object...)}. @@ -31,8 +35,11 @@ public class LocalizedMessageFactory extends AbstractMessageFactory { private static final long serialVersionUID = -1996295808703146741L; + @Nullable // FIXME: cannot use ResourceBundle name for serialization until Java 8 - private transient final ResourceBundle resourceBundle; + private final transient ResourceBundle resourceBundle; + + @Nullable private final String baseName; public LocalizedMessageFactory(final ResourceBundle resourceBundle) { @@ -69,11 +76,11 @@ public ResourceBundle getResourceBundle() { @Override public Message newMessage(final String key) { if (resourceBundle == null) { - return new LocalizedMessage(baseName, key); + return new LocalizedMessage(baseName, key); } return new LocalizedMessage(resourceBundle, key); } - + /** * Creates {@link LocalizedMessage} instances. * @@ -91,4 +98,20 @@ public Message newMessage(final String key, final Object... params) { return new LocalizedMessage(resourceBundle, key, params); } + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final LocalizedMessageFactory that = (LocalizedMessageFactory) object; + return Objects.equals(resourceBundle, that.resourceBundle) && Objects.equals(baseName, that.baseName); + } + + @Override + public int hashCode() { + return Objects.hash(resourceBundle, baseName); + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/LoggerNameAwareMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/LoggerNameAwareMessage.java index a4821a40eea..b86f8b271b3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/LoggerNameAwareMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/LoggerNameAwareMessage.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; 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..f1144cee624 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 @@ -1,25 +1,25 @@ /* * 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.logging.log4j.message; +import java.util.AbstractMap; import java.util.Collections; import java.util.Map; import java.util.TreeMap; - import org.apache.logging.log4j.util.BiConsumer; import org.apache.logging.log4j.util.Chars; import org.apache.logging.log4j.util.EnglishEnums; @@ -57,36 +57,50 @@ 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 + * @since 2.8 */ public static MapFormat lookupIgnoreCase(final String format) { - return XML.name().equalsIgnoreCase(format) ? XML // - : JSON.name().equalsIgnoreCase(format) ? JSON // - : JAVA.name().equalsIgnoreCase(format) ? JAVA // - : null; + 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. + * @since 2.8 */ 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,8 +115,9 @@ public MapMessage() { /** * Constructs a new instance. - * + * * @param initialCapacity the initial capacity. + * @since 2.9.0 */ public MapMessage(final int initialCapacity) { this.data = new SortedArrayStringMap(initialCapacity); @@ -177,7 +192,7 @@ public void clear() { * * @param key the key whose presence to check. May be {@code null}. * @return {@code true} if this data structure contains the specified key, {@code false} otherwise - * @since 2.9 + * @since 2.9.0 */ public boolean containsKey(final String key) { return data.containsKey(key); @@ -185,13 +200,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 +228,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); } @@ -228,7 +244,7 @@ public String remove(final String key) { } /** - * Formats the Structured data as described in RFC 5424. + * Formats the Structured data as described in RFC 5424. * * @return The formatted String. */ @@ -237,19 +253,20 @@ public String asString() { } /** - * Formats the Structured data as described in RFC 5424. + * Formats the Structured data as described in RFC 5424. * * @param format The format identifier. * @return The formatted String. */ public String asString(final String format) { try { - return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString(); + return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()) + .toString(); } catch (final IllegalArgumentException ex) { 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. @@ -263,10 +280,9 @@ public String asString(final String format) { * @param action The action to be performed for each key-value pair in this collection * @param type of the consumer value * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications - * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or - * {@link #forEach(TriConsumer, Object)}. + * to this data structure while iterating over the contents. * @see ReadOnlyStringMap#forEach(BiConsumer) - * @since 2.9 + * @since 2.9.0 */ public void forEach(final BiConsumer action) { data.forEach(action); @@ -292,17 +308,16 @@ public void forEach(final BiConsumer action) { * @param type of the consumer value * @param type of the third parameter * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications - * to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or - * {@link #forEach(TriConsumer, Object)}. + * to this data structure while iterating over the contents. * @see ReadOnlyStringMap#forEach(TriConsumer, Object) - * @since 2.9 + * @since 2.9.0 */ public void forEach(final TriConsumer action, final S state) { data.forEach(action, state); } - + /** - * Formats the Structured data as described in RFC 5424. + * Formats the Structured data as described in RFC 5424. * * @param format The format identifier. * @return The formatted String. @@ -312,19 +327,22 @@ private StringBuilder format(final MapFormat format, final StringBuilder sb) { appendMap(sb); } else { switch (format) { - case XML : { + case XML: { asXml(sb); break; } - case JSON : { + case JSON: { asJson(sb); break; } - case JAVA : { + case JAVA: { asJava(sb); break; } - default : { + case JAVA_UNQUOTED: + asJavaUnquoted(sb); + break; + default: { appendMap(sb); } } @@ -340,11 +358,9 @@ private StringBuilder format(final MapFormat format, final StringBuilder sb) { public void asXml(final StringBuilder sb) { sb.append("\n"); for (int i = 0; i < data.size(); i++) { - sb.append(" "); - int size = sb.length(); - ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); + sb.append(" "); + final int size = sb.length(); + ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb); StringBuilders.escapeXml(sb, size); sb.append("\n"); } @@ -365,8 +381,9 @@ public String getFormattedMessage() { * @param formats * An array of Strings that provide extra information about how to format the message. MapMessage uses * the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default - * format is key1="value1" key2="value2" as required by RFC - * 5424 messages. + * format is key1="value1" key2="value2" as required by + * RFC 5424 + * messages. * * @return The formatted message. */ @@ -394,40 +411,40 @@ 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); } + /** + * @since 2.11.2 + */ + 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 +470,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); } @@ -462,7 +479,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || this.getClass() != o.getClass()) { + if (!(o instanceof MapMessage)) { return false; } @@ -485,11 +502,11 @@ public int hashCode() { public Throwable getThrowable() { return null; } - + /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final boolean value) { // do nothing @@ -497,8 +514,8 @@ protected void validate(final String key, final boolean value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final byte value) { // do nothing @@ -506,8 +523,8 @@ protected void validate(final String key, final byte value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final char value) { // do nothing @@ -515,8 +532,8 @@ protected void validate(final String key, final char value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final double value) { // do nothing @@ -524,17 +541,17 @@ protected void validate(final String key, final double value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final float value) { // do nothing } - + /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final int value) { // do nothing @@ -542,17 +559,17 @@ protected void validate(final String key, final int value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final long value) { // do nothing } - + /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final Object value) { // do nothing @@ -560,8 +577,8 @@ protected void validate(final String key, final Object value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final short value) { // do nothing @@ -569,22 +586,34 @@ protected void validate(final String key, final short value) { /** * Default implementation does nothing. - * - * @since 2.9 + * + * @since 2.9.0 */ protected void validate(final String key, final String value) { // do nothing } + /** + * Allows subclasses to change a candidate key to an actual key. + * + * @param candidateKey The candidate key. + * @return The candidate key. + * @since 2.12.0 + */ + protected String toKey(final String candidateKey) { + return candidateKey; + } + /** * 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 + * @since 2.9.0 */ @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 +621,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 + * @since 2.9.0 */ @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,28 +636,29 @@ 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 + * @since 2.9.0 */ @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; } - /** * 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 + * @since 2.9.0 */ @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 +666,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 + * @since 2.9.0 */ @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 +681,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 + * @since 2.9.0 */ @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 +696,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 + * @since 2.9.0 */ @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 +711,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 + * @since 2.9.0 */ @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 +726,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 + * @since 2.9.0 */ @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,14 +741,15 @@ 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} + * @since 2.5 */ @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..c5e5127f533 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java @@ -0,0 +1,393 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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 + * log4j2.mapMessage.jsonFormatter.maxDepth 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("log4j2.mapMessage.jsonFormatter.maxDepth", 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((double) 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/Message.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/Message.java index 42dc9d5d9a6..1b1dcaab265 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/Message.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/Message.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -69,17 +69,15 @@ public interface Message extends Serializable { String getFormattedMessage(); /** - * Gets the format portion of the Message. + * This method has unclear semantics and inconsistent implementations – its usage is strongly discouraged. * - * @return The message format. Some implementations, such as ParameterizedMessage, will use this as - * the message "pattern". Other Messages may simply return an empty String. - * TODO Do all messages have a format? What syntax? Using a Formatter object could be cleaner. - * (RG) In SimpleMessage the format is identical to the formatted message. In ParameterizedMessage and - * StructuredDataMessage it is not. It is up to the Message implementer to determine what this - * method will return. A Formatter is inappropriate as this is very specific to the Message - * implementation so it isn't clear to me how having a Formatter separate from the Message would be cleaner. + * @deprecated Deprecated since version {@code 2.24.0}. + * Use {@link MultiformatMessage} instead to implement messages that can format themselves in one or more encodings. */ - String getFormat(); + @Deprecated + default String getFormat() { + return null; + } /** * Gets parameter values, if any. diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageCollectionMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageCollectionMessage.java index 19d083e0891..0b683a3c98d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageCollectionMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageCollectionMessage.java @@ -1,26 +1,24 @@ /* * 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.logging.log4j.message; /** * A Message that is a collection of Messages. * @param The Message type. + * @since 2.9.0 */ -public interface MessageCollectionMessage extends Message, Iterable { - - -} +public interface MessageCollectionMessage extends Message, Iterable {} 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..62a433469da 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; 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..755f89307cc 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -23,7 +23,7 @@ * @since 2.6 */ public interface MessageFactory2 extends MessageFactory { - + /** * Creates a new message for the specified CharSequence. * @param charSequence the (potentially mutable) CharSequence @@ -137,8 +137,8 @@ public interface MessageFactory2 extends MessageFactory { * @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); + 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. @@ -156,8 +156,17 @@ Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, O * @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); + 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. @@ -176,6 +185,16 @@ Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, O * @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); + 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/MessageFormatMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java index b34a72d57ee..609bf77f4e8 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.message; import java.io.IOException; @@ -24,20 +23,20 @@ import java.util.Arrays; import java.util.IllegalFormatException; import java.util.Locale; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; /** * Handles messages that consist of a format string conforming to java.text.MessageFormat. - * - * @serial In version 2.1, due to a bug in the serialization format, the serialization format was changed along with - * its {@code serialVersionUID} value. */ public class MessageFormatMessage implements Message { private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * @serial In version 2.1, due to a bug in the serialization format, the serialization format was changed along with + * its {@code serialVersionUID} value. + */ private static final long serialVersionUID = 1L; private static final int HASHVAL = 31; @@ -51,7 +50,7 @@ public class MessageFormatMessage implements Message { /** * Constructs a message. - * + * * @param locale the locale for this message format * @param messagePattern the pattern for this message format * @param parameters The objects to format @@ -69,7 +68,7 @@ public MessageFormatMessage(final Locale locale, final String messagePattern, fi /** * Constructs a message. - * + * * @param messagePattern the pattern for this message format * @param parameters The objects to format */ @@ -115,7 +114,7 @@ protected String formatMessage(final String msgPattern, final Object... args) { final MessageFormat temp = new MessageFormat(msgPattern, locale); return temp.format(args); } catch (final IllegalFormatException ife) { - LOGGER.error("Unable to format msg: " + msgPattern, ife); + LOGGER.error("Unable to format msg: {}", msgPattern, ife); return msgPattern; } } @@ -125,7 +124,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof MessageFormatMessage)) { return false; } @@ -144,7 +143,6 @@ public int hashCode() { return result; } - @Override public String toString() { return getFormattedMessage(); 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..8cc82d73de9 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 @@ -1,26 +1,27 @@ /* * 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.logging.log4j.message; /** * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by * extension.) - * - *

Note to implementors

+ *

+ * Note to implementors: + *

*

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

@@ -31,9 +32,7 @@ public class MessageFormatMessageFactory extends AbstractMessageFactory { /** * Constructs a message factory with default flow strings. */ - public MessageFormatMessageFactory() { - super(); - } + public MessageFormatMessageFactory() {} /** * Creates {@link org.apache.logging.log4j.message.StringFormattedMessage} instances. @@ -76,7 +75,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @since 2.6.1 */ @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new MessageFormatMessage(message, p0, p1, p2, p3); } @@ -84,7 +84,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new MessageFormatMessage(message, p0, p1, p2, p3, p4); } @@ -92,7 +93,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new MessageFormatMessage(message, p0, p1, p2, p3, p4, p5); } @@ -100,7 +108,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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, + 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 new MessageFormatMessage(message, p0, p1, p2, p3, p4, p5, p6); } @@ -109,8 +124,16 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new MessageFormatMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } @@ -118,8 +141,17 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new MessageFormatMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @@ -127,8 +159,18 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new MessageFormatMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MultiformatMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MultiformatMessage.java index 2fdf38bb535..2c817258aa3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MultiformatMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MultiformatMessage.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java index dc11a5673b6..ffd83974b0a 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java @@ -1,134 +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.message; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Arrays; - -/** - * Handles messages that contain an Object[]. - *

- * Created for use with the CSV layout. For example: - *

- *

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

- * - * @since 2.4 - */ -public final class ObjectArrayMessage implements Message { - - private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - private static final long serialVersionUID = -5903272448334166185L; - - private transient Object[] array; - private transient String arrayString; - - /** - * Creates the ObjectMessage. - * - * @param obj - * The Object to format. - */ - public ObjectArrayMessage(final Object... obj) { - this.array = obj == null ? EMPTY_OBJECT_ARRAY : obj; - } - - private boolean equalObjectsOrStrings(final Object[] left, final Object[] right) { - return Arrays.equals(left, right) || Arrays.toString(left).equals(Arrays.toString(right)); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final ObjectArrayMessage that = (ObjectArrayMessage) o; - return array == null ? that.array == null : equalObjectsOrStrings(array, that.array); - } - - /** - * Returns the object formatted using its toString method. - * - * @return the String representation of the object. - */ - @Override - public String getFormat() { - return getFormattedMessage(); - } - - /** - * Returns the formatted object message. - * - * @return the formatted object message. - */ - @Override - public String getFormattedMessage() { - // LOG4J2-763: cache formatted string in case obj changes later - if (arrayString == null) { - arrayString = Arrays.toString(array); - } - return arrayString; - } - - /** - * Returns the object as if it were a parameter. - * - * @return The object. - */ - @Override - public Object[] getParameters() { - return array; - } - - /** - * Returns null. - * - * @return null. - */ - @Override - public Throwable getThrowable() { - return null; - } - - @Override - public int hashCode() { - return Arrays.hashCode(array); - } - - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - array = (Object[]) in.readObject(); - } - - @Override - public String toString() { - return getFormattedMessage(); - } - - private void writeObject(final ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - out.writeObject(array); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import org.apache.logging.log4j.util.Constants; + +/** + * Handles messages that contain an Object[]. + *

+ * Created for use with the CSV layout. For example: + *

+ *

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

+ * + * @since 2.4 + */ +public final class ObjectArrayMessage implements Message { + + private static final long serialVersionUID = -5903272448334166185L; + + private transient Object[] array; + private transient String arrayString; + + /** + * Creates the ObjectMessage. + * + * @param obj + * The Object to format. + */ + public ObjectArrayMessage(final Object... obj) { + this.array = obj == null ? Constants.EMPTY_OBJECT_ARRAY : obj; + } + + private boolean equalObjectsOrStrings(final Object[] left, final Object[] right) { + return Arrays.equals(left, right) || Arrays.toString(left).equals(Arrays.toString(right)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ObjectArrayMessage that = (ObjectArrayMessage) o; + return array == null ? that.array == null : equalObjectsOrStrings(array, that.array); + } + + /** + * Returns the object formatted using its toString method. + * + * @return the String representation of the object. + */ + @Override + public String getFormat() { + return getFormattedMessage(); + } + + /** + * Returns the formatted object message. + * + * @return the formatted object message. + */ + @Override + public String getFormattedMessage() { + // LOG4J2-763: cache formatted string in case obj changes later + if (arrayString == null) { + arrayString = Arrays.toString(array); + } + return arrayString; + } + + /** + * Returns the object as if it were a parameter. + * + * @return The object. + */ + @Override + public Object[] getParameters() { + return array; + } + + /** + * Returns null. + * + * @return null. + */ + @Override + public Throwable getThrowable() { + return null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(array); + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + array = (Object[]) in.readObject(); + } + + @Override + public String toString() { + return getFormattedMessage(); + } + + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(array); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectMessage.java index ecd874ce630..c0e02adfd78 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectMessage.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -20,16 +20,16 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; - import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.StringBuilders; +import org.apache.logging.log4j.util.internal.SerializationUtil; /** * Handles messages that contain an Object. */ public class ObjectMessage implements Message, StringBuilderFormattable { - private static final long serialVersionUID = -5903272448334166185L; + private static final long serialVersionUID = -5732356316298601755L; private transient Object obj; private transient String objectString; @@ -101,7 +101,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof ObjectMessage)) { return false; } @@ -125,16 +125,14 @@ public String toString() { private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); - if (obj instanceof Serializable) { - out.writeObject(obj); - } else { - out.writeObject(String.valueOf(obj)); - } + SerializationUtil.writeWrappedObject( + obj instanceof Serializable ? (Serializable) obj : String.valueOf(obj), out); } private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + SerializationUtil.assertFiltered(in); in.defaultReadObject(); - obj = in.readObject(); + obj = SerializationUtil.readWrappedObject(in); } /** 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..7bbd8120d3e --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.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.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.0 + */ +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..551f02c7f7b 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 @@ -1,29 +1,33 @@ /* * 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.logging.log4j.message; -import java.text.SimpleDateFormat; +import java.io.Serializable; +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; - +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.StringBuilders; /** @@ -60,312 +64,281 @@ final class ParameterFormatter { private static final char DELIM_STOP = '}'; private static final char ESCAPE_CHAR = '\\'; - private static ThreadLocal threadLocalSimpleDateFormat = new ThreadLocal<>(); + private static final DateTimeFormatter DATE_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(ZoneId.systemDefault()); - private ParameterFormatter() { - } + private static final Logger STATUS_LOGGER = StatusLogger.getLogger(); + + private ParameterFormatter() {} /** - * Counts the number of unescaped placeholders in the given messagePattern. + * Analyzes – finds argument placeholder (i.e., {@literal "{}"}) occurrences, etc. – the given message pattern. + *

+ * Only {@literal "{}"} strings are treated as argument placeholders. + * Escaped or incomplete argument placeholders will be ignored. + * Some invalid argument placeholder examples: + *

+ *
+     * { }
+     * foo\{}
+     * {bar
+     * {buzz}
+     * 
* - * @param messagePattern the message pattern to be analyzed. - * @return the number of unescaped placeholders. + * @param pattern a message pattern to be analyzed + * @param argCount + * The number of arguments to be formatted. + * For instance, for a parametrized message containing 7 placeholders in the pattern and 4 arguments for formatting, analysis will only need to store the index of the first 4 placeholder characters. + * A negative value indicates no limit. + * @return the analysis result */ - static int countArgumentPlaceholders(final String messagePattern) { - if (messagePattern == null) { - return 0; - } - final int length = messagePattern.length(); - int result = 0; - boolean isEscaped = false; - for (int i = 0; i < length - 1; i++) { - final char curChar = messagePattern.charAt(i); - if (curChar == ESCAPE_CHAR) { - isEscaped = !isEscaped; - } else if (curChar == DELIM_START) { - if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) { - result++; - i++; - } - isEscaped = false; - } else { - isEscaped = false; - } - } - return result; + static MessagePatternAnalysis analyzePattern(final String pattern, final int argCount) { + MessagePatternAnalysis analysis = new MessagePatternAnalysis(); + analyzePattern(pattern, argCount, analysis); + return analysis; } /** - * Counts the number of unescaped placeholders in the given messagePattern. + * Analyzes – finds argument placeholder (i.e., {@literal "{}"}) occurrences, etc. – the given message pattern. + *

+ * Only {@literal "{}"} strings are treated as argument placeholders. + * Escaped or incomplete argument placeholders will be ignored. + * Some invalid argument placeholder examples: + *

+ *
+     * { }
+     * foo\{}
+     * {bar
+     * {buzz}
+     * 
* - * @param messagePattern the message pattern to be analyzed. - * @return the number of unescaped placeholders. + * @param pattern a message pattern to be analyzed + * @param argCount + * The number of arguments to be formatted. + * For instance, for a parametrized message containing 7 placeholders in the pattern and 4 arguments for formatting, analysis will only need to store the index of the first 4 placeholder characters. + * A negative value indicates no limit. + * @param analysis an object to store the results */ - static int countArgumentPlaceholders2(final String messagePattern, final int[] indices) { - if (messagePattern == null) { - return 0; - } - final int length = messagePattern.length(); - int result = 0; - boolean isEscaped = false; - for (int i = 0; i < length - 1; i++) { - final char curChar = messagePattern.charAt(i); - if (curChar == ESCAPE_CHAR) { - isEscaped = !isEscaped; - indices[0] = -1; // escaping means fast path is not available... - result++; - } else if (curChar == DELIM_START) { - if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) { - indices[result] = i; - result++; - i++; - } - isEscaped = false; - } else { - isEscaped = false; - } + static void analyzePattern(final String pattern, final int argCount, final MessagePatternAnalysis analysis) { + + // Short-circuit if there is nothing interesting + final int l; + if (pattern == null || (l = pattern.length()) < 2) { + analysis.placeholderCount = 0; + return; } - return result; - } - /** - * Counts the number of unescaped placeholders in the given messagePattern. - * - * @param messagePattern the message pattern to be analyzed. - * @return the number of unescaped placeholders. - */ - static int countArgumentPlaceholders3(final char[] messagePattern, final int length, final int[] indices) { - int result = 0; - boolean isEscaped = false; - for (int i = 0; i < length - 1; i++) { - final char curChar = messagePattern[i]; - if (curChar == ESCAPE_CHAR) { - isEscaped = !isEscaped; - } else if (curChar == DELIM_START) { - if (!isEscaped && messagePattern[i + 1] == DELIM_STOP) { - indices[result] = i; - result++; - i++; - } - isEscaped = false; + // Count `{}` occurrences that is not escaped, i.e., not `\`-prefixed + boolean escaped = false; + analysis.placeholderCount = 0; + analysis.escapedCharFound = false; + for (int i = 0; i < (l - 1); i++) { + final char c = pattern.charAt(i); + if (c == ESCAPE_CHAR) { + analysis.escapedCharFound = true; + escaped = !escaped; } else { - isEscaped = false; + if (escaped) { + escaped = false; + } else if (c == DELIM_START && pattern.charAt(i + 1) == DELIM_STOP) { + if (argCount < 0 || analysis.placeholderCount < argCount) { + analysis.ensurePlaceholderCharIndicesCapacity(argCount); + analysis.placeholderCharIndices[analysis.placeholderCount++] = i++; + } + // `argCount` is exceeded, skip storing the index + else { + analysis.placeholderCount++; + i++; + } + } } } - return result; } /** - * Replace placeholders in the given messagePattern with arguments. + *See {@link #analyzePattern(String, int, MessagePatternAnalysis)}. * - * @param messagePattern the message pattern containing placeholders. - * @param arguments the arguments to be used to replace placeholders. - * @return the formatted message. */ - static String format(final String messagePattern, final Object[] arguments) { - final StringBuilder result = new StringBuilder(); - final int argCount = arguments == null ? 0 : arguments.length; - formatMessage(result, messagePattern, arguments, argCount); - return result.toString(); - } + static final class MessagePatternAnalysis implements Serializable { - /** - * Replace placeholders in the given messagePattern with arguments. - * - * @param buffer the buffer to write the formatted message into - * @param messagePattern the message pattern containing placeholders. - * @param arguments the arguments to be used to replace placeholders. - */ - static void formatMessage2(final StringBuilder buffer, final String messagePattern, - final Object[] arguments, final int argCount, final int[] indices) { - if (messagePattern == null || arguments == null || argCount == 0) { - buffer.append(messagePattern); - return; - } - int previous = 0; - for (int i = 0; i < argCount; i++) { - buffer.append(messagePattern, previous, indices[i]); - previous = indices[i] + 2; - recursiveDeepToString(arguments[i], buffer, null); + private static final long serialVersionUID = -5974082575968329887L; + + /** + * The size of the {@link #placeholderCharIndices} buffer to be allocated if it is found to be null. + */ + private static final int PLACEHOLDER_CHAR_INDEX_BUFFER_INITIAL_SIZE = 8; + + /** + * The size {@link #placeholderCharIndices} buffer will be extended with if it has found to be insufficient. + */ + private static final int PLACEHOLDER_CHAR_INDEX_BUFFER_SIZE_INCREMENT = 8; + + /** + * The total number of argument placeholder occurrences. + */ + int placeholderCount; + + /** + * The array of indices pointing to the first character of the found argument placeholder occurrences. + */ + int[] placeholderCharIndices; + + /** + * Flag indicating if an escaped (i.e., `\`-prefixed) character is found. + */ + boolean escapedCharFound; + + private void ensurePlaceholderCharIndicesCapacity(final int argCount) { + + // Initialize the index buffer, if necessary + if (placeholderCharIndices == null) { + final int length = Math.max(argCount, PLACEHOLDER_CHAR_INDEX_BUFFER_INITIAL_SIZE); + placeholderCharIndices = new int[length]; + } + + // Extend the index buffer, if necessary + else if (placeholderCount >= placeholderCharIndices.length) { + final int newLength = argCount > 0 + ? argCount + : Math.addExact(placeholderCharIndices.length, PLACEHOLDER_CHAR_INDEX_BUFFER_SIZE_INCREMENT); + final int[] newPlaceholderCharIndices = new int[newLength]; + System.arraycopy(placeholderCharIndices, 0, newPlaceholderCharIndices, 0, placeholderCount); + placeholderCharIndices = newPlaceholderCharIndices; + } } - buffer.append(messagePattern, previous, messagePattern.length()); } /** - * Replace placeholders in the given messagePattern with arguments. + * Format the given pattern using provided arguments. * - * @param buffer the buffer to write the formatted message into - * @param messagePattern the message pattern containing placeholders. - * @param arguments the arguments to be used to replace placeholders. + * @param pattern a formatting pattern + * @param args arguments to be formatted + * @return the formatted message + * @throws IllegalArgumentException on invalid input */ - static void formatMessage3(final StringBuilder buffer, final char[] messagePattern, final int patternLength, - final Object[] arguments, final int argCount, final int[] indices) { - if (messagePattern == null) { - return; - } - if (arguments == null || argCount == 0) { - buffer.append(messagePattern); - return; - } - int previous = 0; - for (int i = 0; i < argCount; i++) { - buffer.append(messagePattern, previous, indices[i]); - previous = indices[i] + 2; - recursiveDeepToString(arguments[i], buffer, null); - } - buffer.append(messagePattern, previous, patternLength); + static String format(final String pattern, final Object[] args, int argCount) { + final StringBuilder result = new StringBuilder(); + final MessagePatternAnalysis analysis = analyzePattern(pattern, argCount); + formatMessage(result, pattern, args, argCount, analysis); + return result.toString(); } /** - * Replace placeholders in the given messagePattern with arguments. + * Format the given pattern using provided arguments into the buffer pointed. * - * @param buffer the buffer to write the formatted message into - * @param messagePattern the message pattern containing placeholders. - * @param arguments the arguments to be used to replace placeholders. + * @param buffer a buffer the formatted output will be written to + * @param pattern a formatting pattern + * @param args arguments to be formatted + * @throws IllegalArgumentException on invalid input */ - static void formatMessage(final StringBuilder buffer, final String messagePattern, - final Object[] arguments, final int argCount) { - if (messagePattern == null || arguments == null || argCount == 0) { - buffer.append(messagePattern); + static void formatMessage( + final StringBuilder buffer, + final String pattern, + final Object[] args, + final int argCount, + final MessagePatternAnalysis analysis) { + + // Short-circuit if there is nothing interesting + if (pattern == null || args == null || analysis.placeholderCount == 0) { + buffer.append(pattern); return; } - int escapeCounter = 0; - int currentArgument = 0; - int i = 0; - final int len = messagePattern.length(); - for (; i < len - 1; i++) { // last char is excluded from the loop - final char curChar = messagePattern.charAt(i); - if (curChar == ESCAPE_CHAR) { - escapeCounter++; - } else { - if (isDelimPair(curChar, messagePattern, i)) { // looks ahead one char - i++; - - // write escaped escape chars - writeEscapedEscapeChars(escapeCounter, buffer); - if (isOdd(escapeCounter)) { - // i.e. escaped: write escaped escape chars - writeDelimPair(buffer); - } else { - // unescaped - writeArgOrDelimPair(arguments, argCount, currentArgument, buffer); - currentArgument++; - } - } else { - handleLiteralChar(buffer, escapeCounter, curChar); - } - escapeCounter = 0; + // #2380: check if the count of placeholder is not equal to the count of arguments + if (analysis.placeholderCount != argCount) { + final int noThrowableArgCount = + argCount < 1 ? 0 : argCount - ((args[argCount - 1] instanceof Throwable) ? 1 : 0); + if (analysis.placeholderCount != noThrowableArgCount) { + STATUS_LOGGER.warn( + "found {} argument placeholders, but provided {} for pattern `{}`", + analysis.placeholderCount, + argCount, + pattern); } } - handleRemainingCharIfAny(messagePattern, len, buffer, escapeCounter, i); - } - - /** - * Returns {@code true} if the specified char and the char at {@code curCharIndex + 1} in the specified message - * pattern together form a "{}" delimiter pair, returns {@code false} otherwise. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 22 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static boolean isDelimPair(final char curChar, final String messagePattern, final int curCharIndex) { - return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 1) == DELIM_STOP; - } - /** - * Detects whether the message pattern has been fully processed or if an unprocessed character remains and processes - * it if necessary, returning the resulting position in the result char array. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void handleRemainingCharIfAny(final String messagePattern, final int len, - final StringBuilder buffer, final int escapeCounter, final int i) { - if (i == len - 1) { - final char curChar = messagePattern.charAt(i); - handleLastChar(buffer, escapeCounter, curChar); + // Slow-path for patterns containing escapes + if (analysis.escapedCharFound) { + formatMessageContainingEscapes(buffer, pattern, args, argCount, analysis); } - } - /** - * Processes the last unprocessed character and returns the resulting position in the result char array. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void handleLastChar(final StringBuilder buffer, final int escapeCounter, final char curChar) { - if (curChar == ESCAPE_CHAR) { - writeUnescapedEscapeChars(escapeCounter + 1, buffer); - } else { - handleLiteralChar(buffer, escapeCounter, curChar); + // Fast-path for patterns containing no escapes + else { + formatMessageContainingNoEscapes(buffer, pattern, args, argCount, analysis); } } - /** - * Processes a literal char (neither an '\' escape char nor a "{}" delimiter pair) and returns the resulting - * position. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 16 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void handleLiteralChar(final StringBuilder buffer, final int escapeCounter, final char curChar) { - // any other char beside ESCAPE or DELIM_START/STOP-combo - // write unescaped escape chars - writeUnescapedEscapeChars(escapeCounter, buffer); - buffer.append(curChar); - } - - /** - * Writes "{}" to the specified result array at the specified position and returns the resulting position. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 18 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void writeDelimPair(final StringBuilder buffer) { - buffer.append(DELIM_START); - buffer.append(DELIM_STOP); - } - - /** - * Returns {@code true} if the specified parameter is odd. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static boolean isOdd(final int number) { - return (number & 1) == 1; - } - - /** - * Writes a '\' char to the specified result array (starting at the specified position) for each pair of - * '\' escape chars encountered in the message format and returns the resulting position. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void writeEscapedEscapeChars(final int escapeCounter, final StringBuilder buffer) { - final int escapedEscapes = escapeCounter >> 1; // divide by two - writeUnescapedEscapeChars(escapedEscapes, buffer); + private static void formatMessageContainingNoEscapes( + final StringBuilder buffer, + final String pattern, + final Object[] args, + final int argCount, + final MessagePatternAnalysis analysis) { + + // Format each argument and the text preceding it + int precedingTextStartIndex = 0; + final int argLimit = Math.min(analysis.placeholderCount, argCount); + for (int argIndex = 0; argIndex < argLimit; argIndex++) { + final int placeholderCharIndex = analysis.placeholderCharIndices[argIndex]; + buffer.append(pattern, precedingTextStartIndex, placeholderCharIndex); + recursiveDeepToString(args[argIndex], buffer); + precedingTextStartIndex = placeholderCharIndex + 2; + } + + // Format the last trailing text + buffer.append(pattern, precedingTextStartIndex, pattern.length()); } - /** - * Writes the specified number of '\' chars to the specified result array (starting at the specified position) and - * returns the resulting position. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 20 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void writeUnescapedEscapeChars(int escapeCounter, final StringBuilder buffer) { - while (escapeCounter > 0) { - buffer.append(ESCAPE_CHAR); - escapeCounter--; - } + private static void formatMessageContainingEscapes( + final StringBuilder buffer, + final String pattern, + final Object[] args, + final int argCount, + final MessagePatternAnalysis analysis) { + + // Format each argument and the text preceding it + int precedingTextStartIndex = 0; + final int argLimit = Math.min(analysis.placeholderCount, argCount); + for (int argIndex = 0; argIndex < argLimit; argIndex++) { + final int placeholderCharIndex = analysis.placeholderCharIndices[argIndex]; + copyMessagePatternContainingEscapes(buffer, pattern, precedingTextStartIndex, placeholderCharIndex); + recursiveDeepToString(args[argIndex], buffer); + precedingTextStartIndex = placeholderCharIndex + 2; + } + + // Format the last trailing text + copyMessagePatternContainingEscapes(buffer, pattern, precedingTextStartIndex, pattern.length()); } - /** - * Appends the argument at the specified argument index (or, if no such argument exists, the "{}" delimiter pair) to - * the specified result char array at the specified position and returns the resulting position. - */ - // Profiling showed this method is important to log4j performance. Modify with care! - // 25 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 - private static void writeArgOrDelimPair(final Object[] arguments, final int argCount, final int currentArgument, - final StringBuilder buffer) { - if (currentArgument < argCount) { - recursiveDeepToString(arguments[currentArgument], buffer, null); - } else { - writeDelimPair(buffer); + private static void copyMessagePatternContainingEscapes( + final StringBuilder buffer, final String pattern, final int startIndex, final int endIndex) { + boolean escaped = false; + int i = startIndex; + for (; i < endIndex; i++) { + final char c = pattern.charAt(i); + if (c == ESCAPE_CHAR) { + if (escaped) { + // Found an escaped `\`, skip appending it + escaped = false; + } else { + escaped = true; + buffer.append(c); + } + } else { + if (escaped) { + if (c == DELIM_START && pattern.charAt(i + 1) == DELIM_STOP) { + // Found an escaped placeholder, override the earlier appended `\` + buffer.setLength(buffer.length() - 1); + buffer.append("{}"); + i++; + } else { + buffer.append(c); + } + escaped = false; + } else { + buffer.append(c); + } + } } } @@ -420,35 +393,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 +459,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)); + DATE_FORMATTER.formatTo(((Date) o).toInstant(), str); 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 +470,8 @@ 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,37 +479,37 @@ 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, - final Class oClass) { + 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)); + appendArray((byte[]) o, str); } else if (oClass == short[].class) { - str.append(Arrays.toString((short[]) o)); + appendArray((short[]) o, str); } else if (oClass == int[].class) { - str.append(Arrays.toString((int[]) o)); + appendArray((int[]) o, str); } else if (oClass == long[].class) { - str.append(Arrays.toString((long[]) o)); + appendArray((long[]) o, str); } else if (oClass == float[].class) { - str.append(Arrays.toString((float[]) o)); + appendArray((float[]) o, str); } else if (oClass == double[].class) { - str.append(Arrays.toString((double[]) o)); + appendArray((double[]) o, str); } else if (oClass == boolean[].class) { - str.append(Arrays.toString((boolean[]) o)); + appendArray((boolean[]) o, str); } else if (oClass == char[].class) { - str.append(Arrays.toString((char[]) o)); + appendArray((char[]) o, str); } else { - if (dejaVu == null) { - dejaVu = new HashSet<>(); - } // 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,54 +519,52 @@ 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; - for (final Object o1 : oMap.entrySet()) { - final Map.Entry current = (Map.Entry) o1; + for (final Map.Entry entry : oMap.entrySet()) { if (isFirst) { isFirst = false; } else { str.append(", "); } - final Object key = current.getKey(); - final Object value = current.getValue(); - recursiveDeepToString(key, str, new HashSet<>(dejaVu)); + final Object key = entry.getKey(); + final Object value = entry.getValue(); + 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 +574,26 @@ 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 { @@ -651,4 +644,132 @@ static String identityToString(final Object obj) { return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj)); } + /** + * @see Arrays#toString(byte[]) + */ + private static void appendArray(final byte[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(short[]) + */ + private static void appendArray(final short[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(int[]) + */ + // package protected to allow access from ParameterFormatterBenchmark + static void appendArray(final int[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(long[]) + */ + private static void appendArray(final long[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(float[]) + */ + private static void appendArray(final float[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(double[]) + */ + private static void appendArray(final double[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(boolean[]) + */ + private static void appendArray(final boolean[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } + + /** + * @see Arrays#toString(char[]) + */ + private static void appendArray(char[] a, final StringBuilder str) { + int len = a.length; + if (len == 0) { + str.append("[]"); + return; + } + str.append('[').append(a[0]); + for (int i = 1; i < len; i++) { + str.append(", ").append(a[i]); + } + str.append(']'); + } } 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..b9dfb918efe --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterVisitable.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.message; + +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * Allows message parameters to be iterated over without any allocation + * or memory copies. + * + * @since 2.11.0 + */ +@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..a93b495e42e 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 @@ -1,44 +1,57 @@ /* * 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.logging.log4j.message; -import java.util.Arrays; +import static org.apache.logging.log4j.message.ParameterFormatter.analyzePattern; +import static org.apache.logging.log4j.util.StringBuilders.trimToMaxSize; +import com.google.errorprone.annotations.InlineMe; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Objects; +import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis; import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.StringBuilderFormattable; -import org.apache.logging.log4j.util.StringBuilders; +import org.apache.logging.log4j.util.internal.SerializationUtil; /** - * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and - * the parameters. + * A {@link Message} accepting argument placeholders in the formatting pattern. *

- * This class was originally written for Lilith by Joern Huxhorn where it is - * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain. + * Only {@literal "{}"} strings are treated as argument placeholders. + * Escaped (i.e., {@code "\"}-prefixed) or incomplete argument placeholders will be ignored. + * Examples of argument placeholders that will be discarded and rendered intact: *

+ *
+ * { }
+ * foo\{}
+ * {bar
+ * {buzz}
+ * 
*/ public class ParameterizedMessage implements Message, StringBuilderFormattable { - // Should this be configurable? - private static final int DEFAULT_STRING_BUILDER_SIZE = 255; - /** * Prefix for recursion. */ public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX; + /** * Suffix for recursion. */ @@ -66,126 +79,154 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable { private static final long serialVersionUID = -665975803997290697L; - private static final int HASHVAL = 31; + private static final ThreadLocal FORMAT_BUFFER_HOLDER_REF = + Constants.ENABLE_THREADLOCALS ? ThreadLocal.withInitial(FormatBufferHolder::new) : null; + + private static final class FormatBufferHolder { + + private final StringBuilder buffer = new StringBuilder(Constants.MAX_REUSABLE_MESSAGE_SIZE); + + private boolean used = false; + } + + private final String pattern; - // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay - private static ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); + private transient Object[] args; - private String messagePattern; - private transient Object[] argArray; + private final transient Throwable throwable; + + private final MessagePatternAnalysis patternAnalysis; private String formattedMessage; - private transient Throwable throwable; - 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 + * Constructs an instance. + *

+ * The {@link Throwable} associated with the message (and returned in {@link #getThrowable()}) will be determined as follows: + *

+ *
    + *
  1. If a {@code throwable} argument is provided
  2. + *
  3. If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern
  4. + *
+ * + * @param pattern a formatting pattern + * @param args arguments to be formatted + * @param throwable a {@link Throwable} + * @deprecated Since 2.6, use {@link #ParameterizedMessage(String, Object[], Throwable)} instead */ + @InlineMe( + replacement = "this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable)", + imports = "java.util.Arrays") @Deprecated - public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) { - this.argArray = arguments; - this.throwable = throwable; - init(messagePattern); + public ParameterizedMessage(final String pattern, final String[] args, final Throwable throwable) { + this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable); } /** - * 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. + * Constructs an instance. + *

+ * The {@link Throwable} associated with the message (and returned in {@link #getThrowable()}) will be determined as follows: + *

+ *
    + *
  1. If a {@code throwable} argument is provided
  2. + *
  3. If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern
  4. + *
+ * + * @param pattern a formatting pattern + * @param args arguments to be formatted + * @param throwable a {@link Throwable} */ - public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) { - this.argArray = arguments; - this.throwable = throwable; - init(messagePattern); + public ParameterizedMessage(final String pattern, final Object[] args, final Throwable throwable) { + this.args = args; + this.pattern = pattern; + this.patternAnalysis = analyzePattern(pattern, args != null ? args.length : 0); + this.throwable = determineThrowable(throwable, this.args, patternAnalysis); + } + + private static Throwable determineThrowable( + final Throwable throwable, final Object[] args, final MessagePatternAnalysis analysis) { + + // Short-circuit if an explicit `Throwable` is provided + if (throwable != null) { + return throwable; + } + + // If the last `Throwable` argument is not consumed in the pattern, use that + if (args != null && args.length > analysis.placeholderCount) { + Object lastArg = args[args.length - 1]; + if (lastArg instanceof Throwable) { + return (Throwable) lastArg; + } + } + + // No `Throwable`s available + return null; } /** - * Constructs a ParameterizedMessage which contains the arguments converted to String as well as an optional - * Throwable. - * - *

If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned - * in {@link #getThrowable()} and won't be contained in the created String[]. - * If it is used up {@link #getThrowable()} will return null even if the last argument was a Throwable!

+ * Constructor with a pattern and multiple arguments. + *

+ * If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()}. + *

* - * @param messagePattern the message pattern that to be checked for placeholders. - * @param arguments the argument array to be converted. + * @param pattern a formatting pattern + * @param args arguments to be formatted */ - public ParameterizedMessage(final String messagePattern, final Object... arguments) { - this.argArray = arguments; - init(messagePattern); + public ParameterizedMessage(final String pattern, final Object... args) { + this(pattern, args, null); } /** - * Constructor with a pattern and a single parameter. - * @param messagePattern The message pattern. - * @param arg The parameter. + * Constructor with a pattern and a single argument. + *

+ * If the argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()}. + *

+ * + * @param pattern a formatting pattern + * @param arg an argument */ - public ParameterizedMessage(final String messagePattern, final Object arg) { - this(messagePattern, new Object[]{arg}); + public ParameterizedMessage(final String pattern, final Object arg) { + this(pattern, new Object[] {arg}); } /** - * Constructor with a pattern and two parameters. - * @param messagePattern The message pattern. - * @param arg0 The first parameter. - * @param arg1 The second parameter. + * Constructor with a pattern and two arguments. + *

+ * If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()} and won't be contained in the formatted message. + *

+ * + * @param pattern a formatting pattern + * @param arg0 the first argument + * @param arg1 the second argument */ - public ParameterizedMessage(final String messagePattern, final Object arg0, final Object arg1) { - this(messagePattern, new Object[]{arg0, arg1}); - } - - private void init(final String messagePattern) { - this.messagePattern = messagePattern; - final int len = Math.max(1, messagePattern == null ? 0 : messagePattern.length() >> 1); // divide by 2 - this.indices = new int[len]; // LOG4J2-1542 ensure non-zero array length - final int placeholders = ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices); - initThrowable(argArray, placeholders); - this.usedCount = Math.min(placeholders, argArray == null ? 0 : argArray.length); - } - - private void initThrowable(final Object[] params, final int usedParams) { - if (params != null) { - final int argCount = params.length; - if (usedParams < argCount && this.throwable == null && params[argCount - 1] instanceof Throwable) { - this.throwable = (Throwable) params[argCount - 1]; - } - } + public ParameterizedMessage(final String pattern, final Object arg0, final Object arg1) { + this(pattern, new Object[] {arg0, arg1}); } /** - * Returns the message pattern. - * @return the message pattern. + * @return the message formatting pattern */ @Override public String getFormat() { - return messagePattern; + return pattern; } /** - * Returns the message parameters. - * @return the message parameters. + * @return the message arguments */ @Override public Object[] getParameters() { - return argArray; + return args; } /** - * Returns the Throwable that was given as the last argument, if any. - * It will not survive serialization. The Throwable exists as part of the message - * primarily so that it can be extracted from the end of the list of parameters - * and then be added to the LogEvent. As such, the Throwable in the event should - * not be used once the LogEvent has been constructed. + * The {@link Throwable} provided along with the message by one of the following means: + *
    + *
  1. explicitly in the constructor
  2. + *
  3. as the last message argument that is not referred to by any placeholder in the formatting pattern
  4. + *
* - * @return the Throwable, if any. + * @return the {@link Throwable} provided along with the message */ @Override public Throwable getThrowable() { @@ -194,90 +235,87 @@ public Throwable getThrowable() { /** * Returns the formatted message. - * @return the formatted message. + *

+ * If possible, the result will be cached for subsequent invocations. + *

+ * + * @return the formatted message */ @Override public String getFormattedMessage() { if (formattedMessage == null) { - final StringBuilder buffer = getThreadLocalStringBuilder(); - formatTo(buffer); - formattedMessage = buffer.toString(); - StringBuilders.trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + final FormatBufferHolder bufferHolder; + // If there isn't a format buffer to reuse + if (FORMAT_BUFFER_HOLDER_REF == null || (bufferHolder = FORMAT_BUFFER_HOLDER_REF.get()).used) { + final StringBuilder buffer = new StringBuilder(Constants.MAX_REUSABLE_MESSAGE_SIZE); + formatTo(buffer); + formattedMessage = buffer.toString(); + } + // If there is a format buffer to reuse + else { + bufferHolder.used = true; + final StringBuilder buffer = bufferHolder.buffer; + try { + formatTo(buffer); + formattedMessage = buffer.toString(); + } finally { + trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + buffer.setLength(0); + bufferHolder.used = false; + } + } } return formattedMessage; } - private static StringBuilder getThreadLocalStringBuilder() { - StringBuilder buffer = threadLocalStringBuilder.get(); - if (buffer == null) { - buffer = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); - threadLocalStringBuilder.set(buffer); - } - buffer.setLength(0); - return buffer; - } - @Override public void formatTo(final StringBuilder buffer) { if (formattedMessage != null) { buffer.append(formattedMessage); } else { - if (indices[0] < 0) { - ParameterFormatter.formatMessage(buffer, messagePattern, argArray, usedCount); - } else { - ParameterFormatter.formatMessage2(buffer, messagePattern, argArray, usedCount, indices); - } + final int argCount = args != null ? args.length : 0; + ParameterFormatter.formatMessage(buffer, pattern, args, argCount, patternAnalysis); } } /** - * Replace placeholders in the given messagePattern with arguments. - * - * @param messagePattern the message pattern containing placeholders. - * @param arguments the arguments to be used to replace placeholders. - * @return the formatted message. + * Returns the formatted message. + * @param pattern a message pattern containing argument placeholders + * @param args arguments to be used to replace placeholders */ - public static String format(final String messagePattern, final Object[] arguments) { - return ParameterFormatter.format(messagePattern, arguments); + public static String format(final String pattern, final Object[] args) { + final int argCount = args != null ? args.length : 0; + return ParameterFormatter.format(pattern, args, argCount); } @Override - public boolean equals(final Object o) { - if (this == o) { + public boolean equals(final Object object) { + if (this == object) { return true; } - if (o == null || getClass() != o.getClass()) { - return false; - } - - final ParameterizedMessage that = (ParameterizedMessage) o; - - if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) { - return false; - } - if (!Arrays.equals(this.argArray, that.argArray)) { + if (!(object instanceof ParameterizedMessage)) { return false; } - //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false; - - return true; + final ParameterizedMessage that = (ParameterizedMessage) object; + return Objects.equals(pattern, that.pattern) && Arrays.equals(args, that.args); } @Override public int hashCode() { - int result = messagePattern != null ? messagePattern.hashCode() : 0; - result = HASHVAL * result + (argArray != null ? Arrays.hashCode(argArray) : 0); + int result = pattern != null ? pattern.hashCode() : 0; + result = 31 * result + (args != null ? Arrays.hashCode(args) : 0); return result; } /** - * Counts the number of unescaped placeholders in the given messagePattern. - * - * @param messagePattern the message pattern to be analyzed. - * @return the number of unescaped placeholders. + * Returns the number of argument placeholders. + * @param pattern the message pattern to be analyzed */ - public static int countArgumentPlaceholders(final String messagePattern) { - return ParameterFormatter.countArgumentPlaceholders(messagePattern); + public static int countArgumentPlaceholders(final String pattern) { + if (pattern == null) { + return 0; + } + return analyzePattern(pattern, -1).placeholderCount; } /** @@ -328,7 +366,28 @@ public static String identityToString(final Object obj) { @Override public String toString() { - return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" + - Arrays.toString(argArray) + ", throwable=" + throwable + ']'; + // Avoid formatting arguments! + // It can cause recursion, which can become pretty unpleasant while troubleshooting. + return "ParameterizedMessage[messagePattern=" + pattern + ", argCount=" + args.length + ", throwableProvided=" + + (throwable != null) + ']'; + } + + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(args.length); + for (final Object arg : args) { + final Serializable serializableArg = arg instanceof Serializable ? (Serializable) arg : String.valueOf(arg); + SerializationUtil.writeWrappedObject(serializableArg, out); + } + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + SerializationUtil.assertFiltered(in); + in.defaultReadObject(); + final int argCount = in.readInt(); + args = new Object[argCount]; + for (int argIndex = 0; argIndex < args.length; argIndex++) { + args[argIndex] = SerializationUtil.readWrappedObject(in); + } } } 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..4df77e3bca6 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -28,8 +28,10 @@ *

* This class is immutable. *

- * - *

Note to implementors

+ * + *

+ * Note to implementors: + *

*

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

@@ -45,9 +47,7 @@ public final class ParameterizedMessageFactory extends AbstractMessageFactory { /** * Constructs a message factory. */ - public ParameterizedMessageFactory() { - super(); - } + public ParameterizedMessageFactory() {} /** * Creates {@link ParameterizedMessage} instances. @@ -91,7 +91,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @since 2.6.1 */ @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new ParameterizedMessage(message, p0, p1, p2, p3); } @@ -99,7 +100,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new ParameterizedMessage(message, p0, p1, p2, p3, p4); } @@ -107,7 +109,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5); } @@ -115,7 +124,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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, + 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 new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6); } @@ -124,8 +140,16 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } @@ -133,8 +157,17 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @@ -142,8 +175,18 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } } 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..88442d18551 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -31,11 +31,14 @@ *

* This class is immutable. *

- *

Note to implementors

+ *

+ * Note to implementors: + *

*

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

+ * @since 2.5 */ public final class ParameterizedNoReferenceMessageFactory extends AbstractMessageFactory { private static final long serialVersionUID = 5027639245636870500L; @@ -79,9 +82,7 @@ public Throwable getThrowable() { /** * Constructs a message factory with default flow strings. */ - public ParameterizedNoReferenceMessageFactory() { - super(); - } + public ParameterizedNoReferenceMessageFactory() {} /** * Instance of ParameterizedStatusMessageFactory. 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..35e8fa3919c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; 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..1e504755587 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.message; import java.io.Serializable; - import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -30,26 +29,29 @@ * @since 2.6 */ @PerformanceSensitive("allocation") +/* + * https://errorprone.info/bugpattern/ThreadLocalUsage + * Instance thread locals are not a problem here, since this class is almost a singleton. + */ +@SuppressWarnings("ThreadLocalUsage") public final class ReusableMessageFactory implements MessageFactory2, Serializable { /** - * Instance of ReusableMessageFactory.. + * Instance of ReusableMessageFactory. */ 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 long serialVersionUID = 1L; + private final transient ThreadLocal threadLocalParameterized = new ThreadLocal<>(); + private final transient ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>(); + private final transient ThreadLocal threadLocalObjectMessage = new ThreadLocal<>(); /** * Constructs a message factory. */ - public ReusableMessageFactory() { - super(); - } + public ReusableMessageFactory() {} - private static ReusableParameterizedMessage getParameterized() { + private ReusableParameterizedMessage getParameterized() { ReusableParameterizedMessage result = threadLocalParameterized.get(); if (result == null) { result = new ReusableParameterizedMessage(); @@ -58,7 +60,7 @@ private static ReusableParameterizedMessage getParameterized() { return result.reserved ? new ReusableParameterizedMessage().reserve() : result.reserve(); } - private static ReusableSimpleMessage getSimple() { + private ReusableSimpleMessage getSimple() { ReusableSimpleMessage result = threadLocalSimpleMessage.get(); if (result == null) { result = new ReusableSimpleMessage(); @@ -67,7 +69,7 @@ private static ReusableSimpleMessage getSimple() { return result; } - private static ReusableObjectMessage getObject() { + private ReusableObjectMessage getObject() { ReusableObjectMessage result = threadLocalObjectMessage.get(); if (result == null) { result = new ReusableObjectMessage(); @@ -77,15 +79,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(); } } @@ -126,44 +128,84 @@ public Message newMessage(final String message, final Object p0, final Object p1 } @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, - final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return getParameterized().set(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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return getParameterized().set(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) { + 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 getParameterized().set(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) { + 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 getParameterized().set(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) { + 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 getParameterized().set(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) { + 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 getParameterized().set(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) { + 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 getParameterized().set(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -182,7 +224,6 @@ public Message newMessage(final String message) { return result; } - /** * Creates {@link ReusableObjectMessage} instances. * @@ -197,4 +238,16 @@ public Message newMessage(final Object message) { result.set(message); return result; } + + private Object writeReplace() { + return new SerializationProxy(); + } + + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 1L; + + private Object readResolve() { + return INSTANCE; + } + } } 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..1b87239f40c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -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,56 @@ 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); + Message message = new ObjectMessage(obj); + // Since `toString()` methods are not always pure functions and might depend on the thread and other context + // values, we format the message and cache the result. + message.getFormattedMessage(); + return message; + } + + /** + * @since 2.11.1 + */ + @Override + public void clear() { + obj = null; + } + + private Object writeReplace() { + return memento(); } } 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..86f4bcf3d57 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 @@ -1,26 +1,27 @@ /* * 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.logging.log4j.message; -import java.util.Arrays; +import static org.apache.logging.log4j.util.StringBuilders.trimToMaxSize; +import java.util.Arrays; +import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis; import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.StringBuilders; /** * Reusable parameterized message. This message is mutable and is not safe to be accessed or modified by multiple @@ -30,27 +31,24 @@ * @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; + private static final int MAX_PARAMS = 10; private static final long serialVersionUID = 7800075879295123856L; - private transient ThreadLocal buffer; // non-static: LOG4J2-1583 private String messagePattern; + private final MessagePatternAnalysis patternAnalysis = new MessagePatternAnalysis(); + private final StringBuilder formatBuffer = new StringBuilder(Constants.MAX_REUSABLE_MESSAGE_SIZE); private int argCount; - private int usedCount; - private final int[] indices = new int[256]; private transient Object[] varargs; - private transient Object[] params = new Object[MAX_PARMS]; + private transient Object[] params = new Object[MAX_PARAMS]; private transient Throwable throwable; transient boolean reserved = false; // LOG4J2-1583 prevent scrambled logs with nested logging calls /** * Creates a reusable message. */ - public ReusableParameterizedMessage() { - } + public ReusableParameterizedMessage() {} private Object[] getTrimmedParams() { return varargs == null ? Arrays.copyOf(params, argCount) : varargs; @@ -66,18 +64,20 @@ public Object[] swapParameters(final Object[] emptyReplacement) { Object[] result; if (varargs == null) { result = params; - if (emptyReplacement.length >= MAX_PARMS) { + if (emptyReplacement.length >= MAX_PARAMS) { params = emptyReplacement; - } else { + } else if (argCount <= emptyReplacement.length) { // Bad replacement! Too small, may blow up future 10-arg messages. - if (argCount <= emptyReplacement.length) { - // copy params into the specified replacement array and return that - System.arraycopy(params, 0, emptyReplacement, 0, argCount); - result = emptyReplacement; - } else { - // replacement array is too small for current content and future content: discard it - params = new Object[MAX_PARMS]; + // 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 + params = new Object[MAX_PARAMS]; } } else { // The returned array will be reused by the caller in future swapParameter() calls. @@ -104,57 +104,74 @@ 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()); + Message message = new ParameterizedMessage(messagePattern, getTrimmedParams()); + // Since `toString()` methods are not always pure functions and might depend on the thread and other context + // values, we format the message and cache the result. + message.getFormattedMessage(); + return message; } - private void init(final String messagePattern, final int argCount, final Object[] paramArray) { + private void init(final String messagePattern, final int argCount, final Object[] args) { this.varargs = null; this.messagePattern = messagePattern; this.argCount = argCount; - final int placeholderCount = count(messagePattern, indices); - initThrowable(paramArray, argCount, placeholderCount); - this.usedCount = Math.min(placeholderCount, argCount); - } - - private static int count(final String messagePattern, final int[] indices) { - try { - // try the fast path first - return ParameterFormatter.countArgumentPlaceholders2(messagePattern, indices); - } catch (final Exception ex) { // fallback if more than int[] length (256) parameter placeholders - return ParameterFormatter.countArgumentPlaceholders(messagePattern); - } + ParameterFormatter.analyzePattern(messagePattern, argCount, patternAnalysis); + this.throwable = determineThrowable(args, argCount, patternAnalysis.placeholderCount); } - private void initThrowable(final Object[] params, final int argCount, final int usedParams) { - if (usedParams < argCount && params[argCount - 1] instanceof Throwable) { - this.throwable = (Throwable) params[argCount - 1]; - } else { - this.throwable = null; + private static Throwable determineThrowable(final Object[] args, final int argCount, final int placeholderCount) { + if (placeholderCount < argCount) { + final Object lastArg = args[argCount - 1]; + if (lastArg instanceof Throwable) { + return (Throwable) lastArg; + } } + return null; } - ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) { + /** + * @since 2.24.0 + */ + public 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) { + /** + * @since 2.24.0 + */ + public 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) { + /** + * @since 2.24.0 + */ + public 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) { + /** + * @since 2.24.0 + */ + public 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 +179,11 @@ 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) { + /** + * @since 2.24.0 + */ + public 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 +192,16 @@ 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) { + /** + * @since 2.24.0 + */ + public 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 +211,17 @@ 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) { + /** + * @since 2.24.0 + */ + public 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 +232,17 @@ 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, + /** + * @since 2.24.0 + */ + public 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,8 +255,19 @@ 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, - final Object p6, final Object p7) { + /** + * @since 2.24.0 + */ + public 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; params[2] = p2; @@ -219,8 +280,20 @@ 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, - final Object p6, final Object p7, final Object p8) { + /** + * @since 2.24.0 + */ + public 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; params[2] = p2; @@ -234,8 +307,21 @@ 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, - final Object p6, final Object p7, final Object p8, final Object p9) { + /** + * @since 2.24.0 + */ + public 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; params[2] = p2; @@ -288,34 +374,18 @@ public Throwable getThrowable() { */ @Override public String getFormattedMessage() { - final StringBuilder sb = getBuffer(); - formatTo(sb); - final String result = sb.toString(); - StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); - return result; - } - - private StringBuilder getBuffer() { - if (buffer == null) { - buffer = new ThreadLocal<>(); - } - StringBuilder result = buffer.get(); - if (result == null) { - final int currentPatternLength = messagePattern == null ? 0 : messagePattern.length(); - result = new StringBuilder(Math.max(MIN_BUILDER_SIZE, currentPatternLength * 2)); - buffer.set(result); + try { + formatTo(formatBuffer); + return formatBuffer.toString(); + } finally { + trimToMaxSize(formatBuffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); + formatBuffer.setLength(0); } - result.setLength(0); - return result; } @Override public void formatTo(final StringBuilder builder) { - if (indices[0] < 0) { - ParameterFormatter.formatMessage(builder, messagePattern, getParams(), argCount); - } else { - ParameterFormatter.formatMessage2(builder, messagePattern, getParams(), usedCount, indices); - } + ParameterFormatter.formatMessage(builder, messagePattern, getParams(), argCount, patternAnalysis); } /** @@ -330,7 +400,32 @@ ReusableParameterizedMessage reserve() { @Override public String toString() { - return "ReusableParameterizedMessage[messagePattern=" + getFormat() + ", stringArgs=" + - Arrays.toString(getParameters()) + ", throwable=" + getThrowable() + ']'; + // Avoid formatting arguments! + // It can cause recursion, which can become pretty unpleasant while troubleshooting. + return "ReusableParameterizedMessage[messagePattern=" + getFormat() + ", argCount=" + getParameterCount() + + ", throwableProvided=" + (getThrowable() != null) + ']'; + } + + /** + * @since 2.11.1 + */ + @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; + // Cut down on the memory usage after an analysis with an excessive argument count + final int placeholderCharIndicesMaxLength = 16; + if (patternAnalysis.placeholderCharIndices != null + && patternAnalysis.placeholderCharIndices.length > placeholderCharIndicesMaxLength) { + patternAnalysis.placeholderCharIndices = new int[placeholderCharIndicesMaxLength]; + } + } + + private Object writeReplace() { + return memento(); } } 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..95f3321de63 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.message; +import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -23,9 +24,8 @@ * @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 CharSequence charSequence; public void set(final String message) { @@ -43,12 +43,12 @@ public String getFormattedMessage() { @Override public String getFormat() { - return getFormattedMessage(); + return charSequence instanceof String ? (String) charSequence : null; } @Override public Object[] getParameters() { - return EMPTY_PARAMS; + return Constants.EMPTY_OBJECT_ARRAY; } @Override @@ -80,9 +80,16 @@ public short getParameterCount() { return 0; } + @Override + public void forEachParameter(final ParameterConsumer action, final S state) {} + @Override public Message memento() { - return new SimpleMessage(charSequence); + SimpleMessage message = new SimpleMessage(charSequence); + // Since `toString()` methods are not always pure functions and might depend on the thread and other context + // values, we format the message and cache the result. + message.getFormattedMessage(); + return message; } // CharSequence impl @@ -101,5 +108,16 @@ public char charAt(final int index) { public CharSequence subSequence(final int start, final int end) { return charSequence.subSequence(start, end); } -} + /** + * @since 2.11.1 + */ + @Override + public void clear() { + charSequence = null; + } + + private Object writeReplace() { + return memento(); + } +} 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..f7f1dd1308d 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 @@ -1,24 +1,25 @@ /* * 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.logging.log4j.message; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.Objects; import org.apache.logging.log4j.util.StringBuilderFormattable; /** @@ -61,12 +62,12 @@ public SimpleMessage(final CharSequence charSequence) { */ @Override public String getFormattedMessage() { - return message = message == null ? String.valueOf(charSequence) : message ; + return message = message == null ? String.valueOf(charSequence) : message; } @Override public void formatTo(final StringBuilder buffer) { - buffer.append(message != null ? message : charSequence); + buffer.append(message != null ? message : charSequence); } /** @@ -75,7 +76,7 @@ public void formatTo(final StringBuilder buffer) { */ @Override public String getFormat() { - return getFormattedMessage(); + return message; } /** @@ -88,17 +89,24 @@ public Object[] getParameters() { } @Override + @SuppressWarnings("UndefinedEquals") public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof SimpleMessage)) { return false; } final SimpleMessage that = (SimpleMessage) o; - return !(charSequence != null ? !charSequence.equals(that.charSequence) : that.charSequence != null); + /* + * https://errorprone.info/bugpattern/UndefinedEquals + * + * If the char sequences are different, we fall back on string comparison. + */ + return Objects.equals(this.charSequence, that.charSequence) + || Objects.equals(this.getFormattedMessage(), that.getFormattedMessage()); } @Override @@ -121,7 +129,6 @@ public Throwable getThrowable() { return null; } - // CharSequence impl @Override @@ -139,7 +146,6 @@ public CharSequence subSequence(final int start, final int end) { return charSequence.subSequence(start, end); } - private void writeObject(final ObjectOutputStream out) throws IOException { getFormattedMessage(); // initialize the message:String field out.defaultWriteObject(); 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..f292c85bde2 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -29,12 +29,13 @@ *

* This class is immutable. *

- * - *

Note to implementors

+ *

+ * Note to implementors: + *

*

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

- * + * * @since 2.5 */ public final class SimpleMessageFactory extends AbstractMessageFactory { @@ -43,6 +44,7 @@ public final class SimpleMessageFactory extends AbstractMessageFactory { * Instance of StringFormatterMessageFactory. */ public static final SimpleMessageFactory INSTANCE = new SimpleMessageFactory(); + private static final long serialVersionUID = 4418995198790088516L; /** @@ -89,7 +91,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @since 2.6.1 */ @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new SimpleMessage(message); } @@ -97,7 +100,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new SimpleMessage(message); } @@ -105,7 +109,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new SimpleMessage(message); } @@ -113,7 +124,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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, + 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 new SimpleMessage(message); } @@ -122,8 +140,16 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new SimpleMessage(message); } @@ -131,8 +157,17 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new SimpleMessage(message); } @@ -140,8 +175,18 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new SimpleMessage(message); } } 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..d1eba763d1c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -22,14 +22,15 @@ import java.util.Arrays; import java.util.IllegalFormatException; import java.util.Locale; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; /** * Handles messages that consist of a format string conforming to {@link java.util.Formatter}. - * - *

Note to implementors

+ * + *

+ * Note to implementors: + *

*

* This class implements the unrolled args API even though StringFormattedMessage does not. This leaves the room for * StringFormattedMessage to unroll itself later. @@ -49,15 +50,15 @@ public class StringFormattedMessage implements Message { private transient String formattedMessage; private transient Throwable throwable; private final Locale locale; - - /** - * Constructs a message. - * - * @param locale the locale for this message format - * @param messagePattern the pattern for this message format - * @param arguments The objects to format - * @since 2.6 - */ + + /** + * Constructs a message. + * + * @param locale the locale for this message format + * @param messagePattern the pattern for this message format + * @param arguments The objects to format + * @since 2.6 + */ public StringFormattedMessage(final Locale locale, final String messagePattern, final Object... arguments) { this.locale = locale; this.messagePattern = messagePattern; @@ -69,7 +70,7 @@ public StringFormattedMessage(final Locale locale, final String messagePattern, /** * Constructs a message. - * + * * @param messagePattern the pattern for this message format * @param arguments The objects to format * @since 2.6 @@ -112,10 +113,14 @@ 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) { - LOGGER.error("Unable to format msg: " + msgPattern, ife); + LOGGER.error("Unable to format msg: {}", msgPattern, ife); return msgPattern; } } @@ -125,7 +130,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof StringFormattedMessage)) { return false; } @@ -145,7 +150,6 @@ public int hashCode() { return result; } - @Override public String toString() { return getFormattedMessage(); 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..4cdb1b52513 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; @@ -28,7 +28,9 @@ *

* This class is immutable. *

- *

Note to implementors

+ *

+ * Note to implementors: + *

*

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

@@ -39,14 +41,13 @@ public final class StringFormatterMessageFactory extends AbstractMessageFactory * Instance of StringFormatterMessageFactory. */ public static final StringFormatterMessageFactory INSTANCE = new StringFormatterMessageFactory(); + private static final long serialVersionUID = -1626332412176965642L; /** * Constructs a message factory with default flow strings. */ - public StringFormatterMessageFactory() { - super(); - } + public StringFormatterMessageFactory() {} /** * Creates {@link StringFormattedMessage} instances. @@ -90,7 +91,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @since 2.6.1 */ @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { return new StringFormattedMessage(message, p0, p1, p2, p3); } @@ -98,7 +100,8 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { return new StringFormattedMessage(message, p0, p1, p2, p3, p4); } @@ -106,7 +109,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5); } @@ -114,7 +124,14 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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, + 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 new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6); } @@ -123,8 +140,16 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); } @@ -132,8 +157,17 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @@ -141,8 +175,18 @@ public Message newMessage(final String message, final Object p0, final Object p1 * @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) { + 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 new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } } 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..545e3533dd3 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 @@ -1,30 +1,28 @@ /* * 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.logging.log4j.message; import java.util.Map; - import org.apache.logging.log4j.util.PerformanceSensitive; /** * A {@link StringMapMessage} typed to {@link String}-only values. This is like the MapMessage class before 2.9. - * - * @since 2.9 + * + * @since 2.9.0 */ @PerformanceSensitive("allocation") @AsynchronouslyFormattable @@ -35,13 +33,11 @@ public class StringMapMessage extends MapMessage { /** * Constructs a new instance. */ - public StringMapMessage() { - super(); - } + public StringMapMessage() {} /** * Constructs a new instance. - * + * * @param initialCapacity * the initial capacity. */ @@ -51,7 +47,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..22a295ad1cb 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 @@ -1,37 +1,37 @@ /* * 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.logging.log4j.message; import java.util.ArrayList; import java.util.Iterator; import java.util.List; - import org.apache.logging.log4j.util.StringBuilderFormattable; /** * A collection of StructuredDataMessages. + * @since 2.9.0 */ -public class StructuredDataCollectionMessage implements StringBuilderFormattable, - MessageCollectionMessage { +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,37 +62,37 @@ 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) { - objects[index++] = obj; - } + for (final Object[] objs : objectList) { + for (final Object obj : objs) { + objects[index++] = obj; + } } return objects; } @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..f8b65d9a81c 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,328 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 com.google.errorprone.annotations.InlineMe; +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. + * @since 2.18.0 + */ + 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.0 + */ + 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.0 + */ + public StructuredDataId(final String name, final int maxLength) { + this(name, null, null, maxLength); + } + + /** + * + * @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's name. + * @since 2.9.0 + */ + 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. + * @since 2.18.0 + */ + 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 Since 2.18.0, use {@link #StructuredDataId(String, String, String[], String[])} instead. + */ + @Deprecated + @InlineMe(replacement = "this(name, String.valueOf(enterpriseNumber), required, optional)") + public StructuredDataId( + final String name, final int enterpriseNumber, final String[] required, final String[] optional) { + this(name, String.valueOf(enterpriseNumber), required, 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.18.0 + */ + 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.0 + * @deprecated Since 2.18.0, use {@link #StructuredDataId(String, String, String[], String[], int)} instead. + */ + @InlineMe(replacement = "this(name, String.valueOf(enterpriseNumber), required, optional, maxLength)") + @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. + * @since 2.18.0 + */ + 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 Since 2.18.0, use {@link StructuredDataId#makeId(String, String)} instead + */ + @Deprecated + // This method should have been `final` from the start, we don't expect anyone to override it. + @InlineMe(replacement = "this.makeId(defaultId, String.valueOf(anEnterpriseNumber))") + public final 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. + * @since 2.18.0 + */ + 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..46b757d54f2 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 @@ -1,24 +1,22 @@ /* * 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.logging.log4j.message; import java.util.Map; - import org.apache.logging.log4j.util.EnglishEnums; import org.apache.logging.log4j.util.StringBuilders; @@ -31,7 +29,7 @@ * values. *

* - * @see RFC 5424 + * @see RFC 5424 */ @AsynchronouslyFormattable public class StructuredDataMessage extends MapMessage { @@ -75,7 +73,7 @@ public StructuredDataMessage(final String id, final String msg, final String typ * @param msg The message. * @param type The message type. * @param maxLength The maximum length of keys; - * @since 2.9 + * @since 2.9.0 */ public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) { this.id = new StructuredDataId(id, null, null, maxLength); @@ -83,7 +81,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. @@ -92,8 +90,7 @@ public StructuredDataMessage(final String id, final String msg, final String typ * @param type The message type. * @param data The StructuredData map. */ - public StructuredDataMessage(final String id, final String msg, final String type, - final Map data) { + public StructuredDataMessage(final String id, final String msg, final String type, final Map data) { this(id, msg, type, data, MAX_LENGTH); } @@ -105,10 +102,10 @@ public StructuredDataMessage(final String id, final String msg, final String typ * @param type The message type. * @param data The StructuredData map. * @param maxLength The maximum length of keys; - * @since 2.9 + * @since 2.9.0 */ - public StructuredDataMessage(final String id, final String msg, final String type, - final Map data, final int maxLength) { + public StructuredDataMessage( + final String id, final String msg, final String type, final Map data, final int maxLength) { super(data); this.id = new StructuredDataId(id, null, null, maxLength); this.message = msg; @@ -132,7 +129,7 @@ public StructuredDataMessage(final StructuredDataId id, final String msg, final * @param msg The message. * @param type The message type. * @param maxLength The maximum length of keys; - * @since 2.9 + * @since 2.9.0 */ public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, final int maxLength) { this.id = id; @@ -149,8 +146,8 @@ public StructuredDataMessage(final StructuredDataId id, final String msg, final * @param type The message type. * @param data The StructuredData map. */ - public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, - final Map data) { + public StructuredDataMessage( + final StructuredDataId id, final String msg, final String type, final Map data) { this(id, msg, type, data, MAX_LENGTH); } @@ -162,10 +159,14 @@ public StructuredDataMessage(final StructuredDataId id, final String msg, final * @param type The message type. * @param data The StructuredData map. * @param maxLength The maximum length of keys; - * @since 2.9 + * @since 2.9.0 */ - public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, - final Map data, final int maxLength) { + public StructuredDataMessage( + final StructuredDataId id, + final String msg, + final String type, + final Map data, + final int maxLength) { super(data); this.id = id; this.message = msg; @@ -173,7 +174,6 @@ public StructuredDataMessage(final StructuredDataId id, final String msg, final this.maxLength = maxLength; } - /** * Constructor based on a StructuredDataMessage. * @param msg The StructuredDataMessage. @@ -253,7 +253,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); } @@ -286,7 +286,6 @@ public String asString() { * @param format The format identifier. Ignored in this implementation. * @return The formatted String. */ - @Override public String asString(final String format) { try { @@ -319,6 +318,7 @@ public final String asString(final Format format, final StructuredDataId structu * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData * will be used. * @param sb The StringBuilder to append the formatted message to. + * @since 2.8 */ public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) { final boolean full = Format.FULL.equals(format); @@ -355,7 +355,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 +373,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 +386,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]; @@ -406,7 +406,6 @@ public String toString() { return asString(null, null); } - @Override public StructuredDataMessage newInstance(final Map map) { return new StructuredDataMessage(this, map); @@ -417,7 +416,7 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof StructuredDataMessage)) { return false; } @@ -468,7 +467,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 +475,7 @@ protected void validate(final String key, final char value) { protected void validate(final String key, final double value) { validateKey(key); } - + /** * @since 2.9 */ @@ -522,18 +521,20 @@ protected void validate(final String key, final String value) { validateKey(key); } + /** + * @since 2.9.0 + */ protected void validateKey(final String key) { if (maxLength > 0 && key.length() > maxLength) { - throw new IllegalArgumentException("Structured data keys are limited to " + maxLength + - " characters. key: " + key); + throw new IllegalArgumentException( + "Structured data keys are limited to " + maxLength + " characters. key: " + key); } for (int i = 0; i < key.length(); i++) { final char c = key.charAt(i); if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') { - throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" + - "and may not contain a space, =, ], or \""); + throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" + + "and may not contain a space, =, ], or \""); } } } - } 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..88ac55fca8c 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 @@ -1,31 +1,36 @@ /* * 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.logging.log4j.message; +import static org.apache.logging.log4j.util.Chars.LF; + +import aQute.bnd.annotation.Cardinality; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceConsumer; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; 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.message.ThreadDumpMessage.ThreadInfoFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.ServiceLoaderUtil; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.Strings; @@ -33,9 +38,10 @@ * Captures information about all running Threads. */ @AsynchronouslyFormattable +@ServiceConsumer(value = ThreadInfoFactory.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.SINGLE) public class ThreadDumpMessage implements Message, StringBuilderFormattable { private static final long serialVersionUID = -1103400781608841088L; - private static ThreadInfoFactory FACTORY; + private static final Lazy FACTORY = Lazy.lazy(ThreadDumpMessage::initFactory); private volatile Map threads; private final String title; @@ -47,7 +53,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,27 +61,13 @@ 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; + private static ThreadInfoFactory initFactory() { + return ServiceLoaderUtil.safeStream( + ThreadInfoFactory.class, + ServiceLoader.load(ThreadInfoFactory.class, ThreadDumpMessage.class.getClassLoader()), + StatusLogger.getLogger()) + .findFirst() + .orElseGet(BasicThreadInfoFactory::new); } @Override @@ -101,13 +93,13 @@ public String getFormattedMessage() { public void formatTo(final StringBuilder sb) { sb.append(title); if (title.length() > 0) { - sb.append('\n'); + sb.append(LF); } for (final Map.Entry entry : threads.entrySet()) { final ThreadInformation info = entry.getKey(); info.printThreadInfo(sb); info.printStack(sb, entry.getValue()); - sb.append('\n'); + sb.append(LF); } } @@ -130,7 +122,7 @@ public Object[] getParameters() { return null; } - /** + /** * Creates a ThreadDumpMessageProxy that can be serialized. * @return a ThreadDumpMessageProxy. */ @@ -138,8 +130,7 @@ protected Object writeReplace() { return new ThreadDumpMessageProxy(this); } - private void readObject(final ObjectInputStream stream) - throws InvalidObjectException { + private void readObject(final ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); } @@ -171,9 +162,10 @@ 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 + * + * @since 2.9.0 */ - public static interface ThreadInfoFactory { + public interface ThreadInfoFactory { Map createThreadInfo(); } @@ -184,8 +176,7 @@ private static class BasicThreadInfoFactory implements ThreadInfoFactory { @Override public Map createThreadInfo() { final Map map = Thread.getAllStackTraces(); - final Map threads = - new HashMap<>(map.size()); + final Map threads = new HashMap<>(map.size()); for (final Map.Entry entry : map.entrySet()) { threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue()); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadInformation.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadInformation.java index f3b4080835e..c9b1d04aa9e 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadInformation.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadInformation.java @@ -1,26 +1,28 @@ /* * 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.logging.log4j.message; /** * Interface used to print basic or extended thread information. + * + * @since 2.9.0 */ public interface ThreadInformation { - + /** * Formats the thread information into the provided StringBuilder. * @param sb The StringBuilder. @@ -33,5 +35,4 @@ public interface ThreadInformation { * @param trace The stack trace element array to format. */ void printStack(StringBuilder sb, StackTraceElement[] trace); - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/TimestampMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/TimestampMessage.java index 4679ab72289..71191229c3c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/TimestampMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/TimestampMessage.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.message; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java index ead2a0b5b23..fbec2d82f42 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/package-info.java @@ -18,4 +18,9 @@ /** * Public Message Types used for Log4j 2. Users may implement their own Messages. */ +@Export +@Version("2.24.2") package org.apache.logging.log4j.message; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/package-info.java index a422f7ba764..5b260752271 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/package-info.java @@ -29,6 +29,11 @@ * used through the {@link org.apache.logging.log4j.ThreadContext} class. *

* - * @see Log4j 2 API manual + * @see Log4j 2 API manual */ +@Export +@Version("2.20.2") package org.apache.logging.log4j; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..1690893187f 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 @@ -1,27 +1,27 @@ /* * 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.logging.log4j.simple; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.PrintStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -58,9 +58,16 @@ 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, + 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) { super(name, messageFactory); final String lvl = props.getStringProperty(SimpleLoggerContext.SYSTEM_PREFIX + name + ".level"); @@ -131,65 +138,124 @@ public boolean isEnabled(final Level testLevel, final Marker marker, final Strin } @Override - public boolean isEnabled(final Level testLevel, final Marker marker, final String message, final Object p0, - final Object p1) { + public boolean isEnabled( + final Level testLevel, final Marker marker, final String message, final Object p0, final Object p1) { return this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { + public boolean isEnabled( + final Level testLevel, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { return this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3) { + public boolean isEnabled( + final Level testLevel, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { return this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, + public boolean isEnabled( + final Level testLevel, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, final Object p4) { return this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public boolean isEnabled( + final Level testLevel, + 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 this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, 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) { + public boolean isEnabled( + final Level testLevel, + 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 this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, 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, + public boolean isEnabled( + final Level testLevel, + 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 this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, 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) { + public boolean isEnabled( + final Level testLevel, + 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 this.level.intLevel() >= testLevel.intLevel(); } @Override - public boolean isEnabled(final Level testLevel, 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) { + public boolean isEnabled( + final Level testLevel, + 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 this.level.intLevel() >= testLevel.intLevel(); } @@ -199,7 +265,14 @@ public boolean isEnabled(final Level testLevel, final Marker marker, final Strin } @Override - public void logMessage(final String fqcn, final Level mgsLevel, final Marker marker, final Message msg, + @SuppressFBWarnings( + value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", + justification = "Log4j prints stacktraces only to logs, which should be private.") + public void logMessage( + final String fqcn, + final Level mgsLevel, + final Marker marker, + final Message msg, final Throwable throwable) { final StringBuilder sb = new StringBuilder(); // Append date-time if so configured @@ -230,7 +303,9 @@ public void logMessage(final String fqcn, final Level mgsLevel, final Marker mar } final Object[] params = msg.getParameters(); Throwable t; - if (throwable == null && params != null && params.length > 0 + if (throwable == null + && params != null + && params.length > 0 && params[params.length - 1] instanceof Throwable) { t = (Throwable) params[params.length - 1]; } else { @@ -252,5 +327,4 @@ public void setLevel(final Level level) { public void setStream(final PrintStream stream) { this.stream = stream; } - } 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..55cced06f58 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 @@ -1,41 +1,40 @@ /* * 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.logging.log4j.simple; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.simple.internal.SimpleProvider; import org.apache.logging.log4j.spi.ExtendedLogger; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.spi.LoggerRegistry; import org.apache.logging.log4j.util.PropertiesUtil; +import org.jspecify.annotations.Nullable; /** - * + * A simple {@link LoggerContext} implementation. */ public class SimpleLoggerContext implements LoggerContext { - private static final String SYSTEM_OUT = "system.out"; - - private static final String SYSTEM_ERR = "system.err"; + /** Singleton instance. */ + static final SimpleLoggerContext INSTANCE = new SimpleLoggerContext(); /** The default format to use when formatting dates */ protected static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz"; @@ -43,6 +42,8 @@ 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 static final MessageFactory DEFAULT_MESSAGE_FACTORY = ParameterizedMessageFactory.INSTANCE; + private final PropertiesUtil props; /** Include the instance name in the log message? */ @@ -53,10 +54,13 @@ public class SimpleLoggerContext implements LoggerContext { * 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; @@ -66,73 +70,86 @@ public class SimpleLoggerContext implements LoggerContext { private final LoggerRegistry loggerRegistry = new LoggerRegistry<>(); + /** + * Constructs a new initialized instance. + */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_OUT", + justification = "Opens a file retrieved from configuration (Log4j properties)") 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; - - final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR); - PrintStream ps; - if (SYSTEM_ERR.equalsIgnoreCase(fileName)) { - ps = System.err; - } else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) { - ps = System.out; - } else { - try { - final FileOutputStream os = new FileOutputStream(fileName); - ps = new PrintStream(os); - } catch (final FileNotFoundException fnfe) { - ps = System.err; - } - } - this.stream = ps; + final SimpleProvider.Config config = SimpleProvider.Config.INSTANCE; + props = config.props; + showContextMap = config.showContextMap; + showLogName = config.showLogName; + showShortName = config.showShortName; + showDateTime = config.showDateTime; + defaultLevel = config.defaultLevel; + dateTimeFormat = config.dateTimeFormat; + stream = config.stream; + } + + @Override + public Object getExternalContext() { + return null; } @Override public ExtendedLogger getLogger(final String name) { - return getLogger(name, null); + return getLogger(name, DEFAULT_MESSAGE_FACTORY); } @Override - public ExtendedLogger getLogger(final String name, final MessageFactory messageFactory) { - // Note: This is the only method where we add entries to the 'loggerRegistry' ivar. - final ExtendedLogger extendedLogger = loggerRegistry.getLogger(name, messageFactory); - if (extendedLogger != null) { - AbstractLogger.checkMessageFactory(extendedLogger, messageFactory); - return extendedLogger; + public ExtendedLogger getLogger(final String name, @Nullable final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + final ExtendedLogger oldLogger = loggerRegistry.getLogger(name, effectiveMessageFactory); + if (oldLogger != null) { + return oldLogger; } - final SimpleLogger simpleLogger = new SimpleLogger(name, defaultLevel, showLogName, showShortName, showDateTime, - showContextMap, dateTimeFormat, messageFactory, props, stream); - loggerRegistry.putIfAbsent(name, messageFactory, simpleLogger); - return loggerRegistry.getLogger(name, messageFactory); + final ExtendedLogger newLogger = createLogger(name, effectiveMessageFactory); + loggerRegistry.putIfAbsent(name, effectiveMessageFactory, newLogger); + return loggerRegistry.getLogger(name, effectiveMessageFactory); } + private ExtendedLogger createLogger(final String name, @Nullable final MessageFactory messageFactory) { + return new SimpleLogger( + name, + defaultLevel, + showLogName, + showShortName, + showDateTime, + showContextMap, + dateTimeFormat, + messageFactory, + props, + stream); + } + + /** + * 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) { - return false; + public boolean hasLogger(final String name) { + return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY); } @Override public boolean hasLogger(final String name, final Class messageFactoryClass) { - return false; + return loggerRegistry.hasLogger(name, messageFactoryClass); } @Override - public Object getExternalContext() { - return null; + public boolean hasLogger(final String name, @Nullable final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + return loggerRegistry.hasLogger(name, effectiveMessageFactory); } - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java index 8ea57a8a5df..163259f2511 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java @@ -1,47 +1,61 @@ /* * 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.logging.log4j.simple; import java.net.URI; - import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.spi.LoggerContextFactory; /** - * + * Simple and stateless {@link LoggerContextFactory}. */ public class SimpleLoggerContextFactory implements LoggerContextFactory { - private static LoggerContext context = new SimpleLoggerContext(); + /** + * Singleton instance. + * + * @since 2.17.2 + */ + public static final SimpleLoggerContextFactory INSTANCE = new SimpleLoggerContextFactory(); @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, - final boolean currentContext) { - return context; + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { + return SimpleLoggerContext.INSTANCE; } @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, - final boolean currentContext, final URI configLocation, final String name) { - return context; + public LoggerContext getContext( + final String fqcn, + final ClassLoader loader, + final Object externalContext, + final boolean currentContext, + final URI configLocation, + final String name) { + return SimpleLoggerContext.INSTANCE; } @Override public void removeContext(final LoggerContext removeContext) { // do nothing } + + @Override + public boolean isClassLoaderDependent() { + return false; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/internal/SimpleProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/internal/SimpleProvider.java new file mode 100644 index 00000000000..e62e2a5c27f --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/internal/SimpleProvider.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.simple.internal; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintStream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.simple.SimpleLoggerContext; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.NoOpThreadContextMap; +import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * A {@link Provider} implementation to use {@link SimpleLoggerContext}. + * + * @since 2.24.0 + */ +@NullMarked +public final class SimpleProvider extends Provider { + + private final ThreadContextMap threadContextMap; + + public SimpleProvider() { + super(null, CURRENT_VERSION); + this.threadContextMap = + Config.INSTANCE.showContextMap ? super.getThreadContextMapInstance() : NoOpThreadContextMap.INSTANCE; + } + + @Override + public LoggerContextFactory getLoggerContextFactory() { + return SimpleLoggerContextFactory.INSTANCE; + } + + @Override + public ThreadContextMap getThreadContextMapInstance() { + return threadContextMap; + } + + public static final class Config { + + /** The default format to use when formatting dates */ + private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz"; + + /** All system properties used by SimpleLog start with this */ + private static final String SYSTEM_PREFIX = "org.apache.logging.log4j.simplelog."; + + private static final String SYSTEM_OUT = "system.out"; + + private static final String SYSTEM_ERR = "system.err"; + + public static final Config INSTANCE = new Config(); + + public final PropertiesUtil props; + + public final boolean showContextMap; + + public final boolean showLogName; + + public final boolean showShortName; + + public final boolean showDateTime; + + public final Level defaultLevel; + + public final @Nullable String dateTimeFormat; + + public final PrintStream stream; + + private Config() { + 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(SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT) + : null; + + final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR); + PrintStream ps; + if (SYSTEM_ERR.equalsIgnoreCase(fileName)) { + ps = System.err; + } else if (SYSTEM_OUT.equalsIgnoreCase(fileName)) { + ps = System.out; + } else { + try { + ps = new PrintStream(new FileOutputStream(fileName)); + } catch (final FileNotFoundException fnfe) { + ps = System.err; + } + } + this.stream = ps; + } + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java index 4f8658dfd38..29139a12ace 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/package-info.java @@ -19,4 +19,9 @@ * Simple logging implementation. This is a rather minimal Log4j Provider that is used by default if no other Log4j * Providers are able to be loaded at runtime. */ +@Export +@Version("2.24.1") package org.apache.logging.log4j.simple; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java index 012dccae0d8..ebe5e5c6680 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java @@ -1,52 +1,49 @@ /* * 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.logging.log4j.spi; import java.io.Serializable; - import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; import org.apache.logging.log4j.LoggingException; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.internal.DefaultLogBuilder; import org.apache.logging.log4j.message.DefaultFlowMessageFactory; 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.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessageFactory; import org.apache.logging.log4j.message.ReusableMessageFactory; -import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.StringFormattedMessage; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.LambdaUtil; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.MessageSupplier; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Supplier; /** * Base implementation of a Logger. It is highly recommended that any Logger implementation extend this class. */ -public abstract class AbstractLogger implements ExtendedLogger, Serializable { +public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLogger, Serializable { // Implementation note: many methods in this class are tuned for performance. MODIFY WITH CARE! // Specifically, try to keep the hot methods to 35 bytecodes or less: // this is within the MaxInlineSize threshold on Java 7 and Java 8 Hotspot and makes these methods @@ -75,25 +72,27 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable { /** * Marker for throwing exceptions. */ - public static final Marker THROWING_MARKER = MarkerManager.getMarker("THROWING").setParents(EXCEPTION_MARKER); + public static final Marker THROWING_MARKER = + MarkerManager.getMarker("THROWING").setParents(EXCEPTION_MARKER); /** * Marker for catching exceptions. */ - public static final Marker CATCHING_MARKER = MarkerManager.getMarker("CATCHING").setParents(EXCEPTION_MARKER); + public static final Marker CATCHING_MARKER = + MarkerManager.getMarker("CATCHING").setParents(EXCEPTION_MARKER); /** * The default MessageFactory class. */ public static final Class DEFAULT_MESSAGE_FACTORY_CLASS = - createClassForProperty("log4j2.messageFactory", ReusableMessageFactory.class, - ParameterizedMessageFactory.class); + ParameterizedMessageFactory.class; /** * The default FlowMessageFactory class. + * @since 2.6 */ public static final Class DEFAULT_FLOW_MESSAGE_FACTORY_CLASS = - createFlowClassForProperty("log4j2.flowMessageFactory", DefaultFlowMessageFactory.class); + DefaultFlowMessageFactory.class; private static final long serialVersionUID = 2L; @@ -104,36 +103,62 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable { protected final String name; private final MessageFactory2 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 + private static final ThreadLocal logBuilder = ThreadLocal.withInitial(DefaultLogBuilder::new); /** - * Creates a new logger named after this class (or subclass). + * Constructs an instance named after this class. */ public AbstractLogger() { - this.name = getClass().getName(); - this.messageFactory = createDefaultMessageFactory(); - this.flowMessageFactory = createDefaultFlowMessageFactory(); + this(null, null, null); } /** - * Creates a new named logger. + * Constructs an instance using the provided name. * - * @param name the logger name + * @param name the logger name (if null, will be derived from this class or subclass) */ public AbstractLogger(final String name) { - this(name, createDefaultMessageFactory()); + this(name, null, null); } /** - * Creates a new named logger with a particular {@link MessageFactory}. + * Constructs an instance using the provided name and {@link MessageFactory}. * - * @param name the logger name - * @param messageFactory the message factory, if null then use the default message factory. + * @param name the logger name (if null, will be derived from this class) + * @param messageFactory the {@link Message} factory (if null, {@link ParameterizedMessageFactory} will be used) */ public AbstractLogger(final String name, final MessageFactory messageFactory) { - this.name = name; - this.messageFactory = messageFactory == null ? createDefaultMessageFactory() : narrow(messageFactory); - this.flowMessageFactory = createDefaultFlowMessageFactory(); + this(name, messageFactory, null); + } + + /** + * The canonical constructor. + * + * @param name the logger name (if null, will be derived from this class) + * @param messageFactory the {@link Message} factory (if null, {@link ParameterizedMessageFactory} will be used) + * @param flowMessageFactory the {@link org.apache.logging.log4j.message.FlowMessage} factory (if null, {@link DefaultFlowMessageFactory} will be used) + * @since 2.24.0 + */ + protected AbstractLogger( + final String name, final MessageFactory messageFactory, final FlowMessageFactory flowMessageFactory) { + if (name != null) { + this.name = name; + } else { + final Class clazz = getClass(); + final String canonicalName = clazz.getCanonicalName(); + this.name = canonicalName != null ? canonicalName : clazz.getName(); + } + this.messageFactory = + messageFactory != null ? adaptMessageFactory(messageFactory) : ParameterizedMessageFactory.INSTANCE; + this.flowMessageFactory = flowMessageFactory != null ? flowMessageFactory : DefaultFlowMessageFactory.INSTANCE; + } + + private static MessageFactory2 adaptMessageFactory(final MessageFactory result) { + if (result instanceof MessageFactory2) { + return (MessageFactory2) result; + } + return new MessageFactory2Adapter(result); } /** @@ -143,28 +168,37 @@ public AbstractLogger(final String name, final MessageFactory messageFactory) { * * @param logger The logger to check * @param messageFactory The message factory to check. + * @deprecated As of version {@code 2.25.0}, planned to be removed! + * Instead, in {@link LoggerContext#getLogger(String, MessageFactory)} implementations, namespace loggers with message factories. + * If your implementation uses {@link LoggerRegistry}, you are already covered. */ + @Deprecated public static void checkMessageFactory(final ExtendedLogger logger, final MessageFactory messageFactory) { final String name = logger.getName(); final MessageFactory loggerMessageFactory = logger.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); + 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)) { - 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()); + 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()); } } @Override - public void catching(final Level level, final Throwable t) { - catching(FQCN, level, t); + public void catching(final Level level, final Throwable throwable) { + catching(FQCN, level, throwable); } /** @@ -172,90 +206,43 @@ public void catching(final Level level, final Throwable t) { * * @param fqcn The fully qualified class name of the caller. * @param level The logging level. - * @param t The Throwable. + * @param throwable The Throwable. */ - protected void catching(final String fqcn, final Level level, final Throwable t) { + protected void catching(final String fqcn, final Level level, final Throwable throwable) { if (isEnabled(level, CATCHING_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, level, CATCHING_MARKER, catchingMsg(t), t); + logMessageSafely(fqcn, level, CATCHING_MARKER, catchingMsg(throwable), throwable); } } @Override - public void catching(final Throwable t) { + public void catching(final Throwable throwable) { if (isEnabled(Level.ERROR, CATCHING_MARKER, (Object) null, null)) { - logMessageSafely(FQCN, Level.ERROR, CATCHING_MARKER, catchingMsg(t), t); + logMessageSafely(FQCN, Level.ERROR, CATCHING_MARKER, catchingMsg(throwable), throwable); } } - protected Message catchingMsg(final Throwable t) { + protected Message catchingMsg(final Throwable throwable) { 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); } @Override - public void debug(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, message, t); + public void debug(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, message, throwable); } @Override - public void debug(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.DEBUG, marker, msg, msg != null ? msg.getThrowable() : null); + public void debug(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.DEBUG, marker, message, message != null ? message.getThrowable() : null); } @Override - public void debug(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, msg, t); + public void debug(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, message, throwable); } @Override @@ -264,8 +251,8 @@ public void debug(final Marker marker, final Object message) { } @Override - public void debug(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, message, t); + public void debug(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, message, throwable); } @Override @@ -279,18 +266,18 @@ public void debug(final Marker marker, final String message, final Object... par } @Override - public void debug(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, message, t); + public void debug(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, message, throwable); } @Override - public void debug(final Message msg) { - logIfEnabled(FQCN, Level.DEBUG, null, msg, msg != null ? msg.getThrowable() : null); + public void debug(final Message message) { + logIfEnabled(FQCN, Level.DEBUG, null, message, message != null ? message.getThrowable() : null); } @Override - public void debug(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, msg, t); + public void debug(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, message, throwable); } @Override @@ -299,8 +286,8 @@ public void debug(final CharSequence message) { } @Override - public void debug(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, message, t); + public void debug(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, message, throwable); } @Override @@ -309,8 +296,8 @@ public void debug(final Object message) { } @Override - public void debug(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, message, t); + public void debug(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, message, throwable); } @Override @@ -324,58 +311,64 @@ public void debug(final String message, final Object... params) { } @Override - public void debug(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, message, t); + public void debug(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, message, throwable); } @Override - public void debug(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.DEBUG, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void debug(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.DEBUG, null, messageSupplier, (Throwable) null); } @Override - public void debug(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void debug(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, messageSupplier, throwable); } @Override - public void debug(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void debug(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.DEBUG, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void debug(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.DEBUG, marker, message, paramSuppliers); } @Override - public void debug(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void debug(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void debug(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.DEBUG, null, message, paramSuppliers); } @Override - public void debug(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, (Throwable) null); + public void debug(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.DEBUG, marker, messageSupplier, (Throwable) null); } @Override - public void debug(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, marker, msgSupplier, t); + public void debug(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, marker, messageSupplier, throwable); } @Override - public void debug(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.DEBUG, null, msgSupplier, (Throwable) null); + public void debug(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.DEBUG, null, messageSupplier, (Throwable) null); } @Override - public void debug(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.DEBUG, null, msgSupplier, t); + public void debug(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.DEBUG, null, messageSupplier, throwable); } @Override @@ -394,48 +387,100 @@ public void debug(final Marker marker, final String message, final Object p0, fi } @Override - public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void debug( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3); } @Override - public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void debug( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4); } @Override - public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void debug( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5, + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void debug(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) { + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void debug(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) { + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void debug(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) { + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -460,41 +505,78 @@ public void debug(final String message, final Object p0, final Object p1, final } @Override - public void debug(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void debug( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4); } @Override - public void debug(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void debug( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void debug(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void debug( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void debug(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void debug(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) { + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void debug(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) { + public void debug( + 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) { logIfEnabled(FQCN, Level.DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -504,11 +586,18 @@ 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. + * @since 2.6 */ + @SuppressWarnings("deprecation") 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); + logMessageSafely( + fqcn, + Level.TRACE, + ENTRY_MARKER, + entryMsg = flowMessageFactory.newEntryMessage(format, LambdaUtil.getAll(paramSuppliers)), + null); } return entryMsg; } @@ -519,6 +608,7 @@ protected EntryMessage enter(final String fqcn, final String format, final Suppl * @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. + * @since 2.6 */ @Deprecated protected EntryMessage enter(final String fqcn, final String format, final MessageSupplier... paramSuppliers) { @@ -535,11 +625,17 @@ 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. + * @since 2.6 */ 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,14 +644,19 @@ 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. + * @param messageSupplier The Supplier of the Message. + * @since 2.6 */ @Deprecated - protected EntryMessage enter(final String fqcn, final MessageSupplier msgSupplier) { + protected EntryMessage enter(final String fqcn, final MessageSupplier messageSupplier) { EntryMessage message = null; if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, message = flowMessageFactory.newEntryMessage( - msgSupplier.get()), null); + logMessageSafely( + fqcn, + Level.TRACE, + ENTRY_MARKER, + message = flowMessageFactory.newEntryMessage(messageSupplier.get()), + null); } return message; } @@ -572,8 +673,8 @@ protected EntryMessage enter(final String fqcn, final MessageSupplier msgSupplie protected EntryMessage enter(final String fqcn, final Message message) { EntryMessage flowMessage = null; if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, flowMessage = flowMessageFactory.newEntryMessage(message), - null); + logMessageSafely( + fqcn, Level.TRACE, ENTRY_MARKER, flowMessage = flowMessageFactory.newEntryMessage(message), null); } return flowMessage; } @@ -584,6 +685,7 @@ public void entry() { entry(FQCN, (Object[]) null); } + @Deprecated @Override public void entry(final Object... params) { entry(FQCN, params); @@ -595,6 +697,7 @@ public void entry(final Object... params) { * @param fqcn The fully qualified class name of the caller. * @param params The parameters to the method. */ + @SuppressWarnings("deprecation") protected void entry(final String fqcn, final Object... params) { if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { if (params == null) { @@ -605,60 +708,40 @@ protected void entry(final String fqcn, final Object... params) { } } + /** + * @since 2.6 + */ 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); } + /** + * @since 2.6 + */ protected EntryMessage entryMsg(final String format, final MessageSupplier... 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(); - params[i] = params[i] != null ? ((Message) params[i]).getFormattedMessage() : null; } return entryMsg(format, params); } + /** + * @since 2.6 + */ 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 - public void error(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.ERROR, marker, msg, msg != null ? msg.getThrowable() : null); + public void error(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.ERROR, marker, message, message != null ? message.getThrowable() : null); } @Override - public void error(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, msg, t); + public void error(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, message, throwable); } @Override @@ -667,8 +750,8 @@ public void error(final Marker marker, final CharSequence message) { } @Override - public void error(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, message, t); + public void error(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, message, throwable); } @Override @@ -677,8 +760,8 @@ public void error(final Marker marker, final Object message) { } @Override - public void error(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, message, t); + public void error(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, message, throwable); } @Override @@ -692,18 +775,18 @@ public void error(final Marker marker, final String message, final Object... par } @Override - public void error(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, message, t); + public void error(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, message, throwable); } @Override - public void error(final Message msg) { - logIfEnabled(FQCN, Level.ERROR, null, msg, msg != null ? msg.getThrowable() : null); + public void error(final Message message) { + logIfEnabled(FQCN, Level.ERROR, null, message, message != null ? message.getThrowable() : null); } @Override - public void error(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, msg, t); + public void error(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, message, throwable); } @Override @@ -712,8 +795,8 @@ public void error(final CharSequence message) { } @Override - public void error(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, message, t); + public void error(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, message, throwable); } @Override @@ -722,8 +805,8 @@ public void error(final Object message) { } @Override - public void error(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, message, t); + public void error(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, message, throwable); } @Override @@ -737,58 +820,64 @@ public void error(final String message, final Object... params) { } @Override - public void error(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, message, t); + public void error(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, message, throwable); } @Override - public void error(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.ERROR, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void error(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.ERROR, null, messageSupplier, (Throwable) null); } @Override - public void error(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void error(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, messageSupplier, throwable); } @Override - public void error(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.ERROR, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void error(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.ERROR, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void error(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.ERROR, marker, message, paramSuppliers); } @Override - public void error(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void error(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void error(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.ERROR, null, message, paramSuppliers); } @Override - public void error(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.ERROR, marker, msgSupplier, (Throwable) null); + public void error(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.ERROR, marker, messageSupplier, (Throwable) null); } @Override - public void error(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, marker, msgSupplier, t); + public void error(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, marker, messageSupplier, throwable); } @Override - public void error(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.ERROR, null, msgSupplier, (Throwable) null); + public void error(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.ERROR, null, messageSupplier, (Throwable) null); } @Override - public void error(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.ERROR, null, msgSupplier, t); + public void error(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.ERROR, null, messageSupplier, throwable); } @Override @@ -807,48 +896,100 @@ public void error(final Marker marker, final String message, final Object p0, fi } @Override - public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void error( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3); } @Override - public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void error( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4); } @Override - public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void error( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5, + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -873,38 +1014,78 @@ public void error(final String message, final Object p0, final Object p1, final } @Override - public void error(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void error( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4); } @Override - public void error(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void error( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void error(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void error( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void error(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) { + public void error( + 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) { logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -930,7 +1111,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; } @@ -942,36 +1123,30 @@ protected R exit(final String fqcn, final R result) { * @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. + * @since 2.6 */ 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; } + /** + * @since 2.6 + */ 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 - public void fatal(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.FATAL, marker, msg, msg != null ? msg.getThrowable() : null); + public void fatal(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.FATAL, marker, message, message != null ? message.getThrowable() : null); } @Override - public void fatal(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, msg, t); + public void fatal(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, message, throwable); } @Override @@ -980,8 +1155,8 @@ public void fatal(final Marker marker, final CharSequence message) { } @Override - public void fatal(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, message, t); + public void fatal(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, message, throwable); } @Override @@ -990,8 +1165,8 @@ public void fatal(final Marker marker, final Object message) { } @Override - public void fatal(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, message, t); + public void fatal(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, message, throwable); } @Override @@ -1005,18 +1180,18 @@ public void fatal(final Marker marker, final String message, final Object... par } @Override - public void fatal(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, message, t); + public void fatal(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, message, throwable); } @Override - public void fatal(final Message msg) { - logIfEnabled(FQCN, Level.FATAL, null, msg, msg != null ? msg.getThrowable() : null); + public void fatal(final Message message) { + logIfEnabled(FQCN, Level.FATAL, null, message, message != null ? message.getThrowable() : null); } @Override - public void fatal(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, msg, t); + public void fatal(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, message, throwable); } @Override @@ -1025,8 +1200,8 @@ public void fatal(final CharSequence message) { } @Override - public void fatal(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, message, t); + public void fatal(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, message, throwable); } @Override @@ -1035,8 +1210,8 @@ public void fatal(final Object message) { } @Override - public void fatal(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, message, t); + public void fatal(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, message, throwable); } @Override @@ -1050,58 +1225,64 @@ public void fatal(final String message, final Object... params) { } @Override - public void fatal(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, message, t); + public void fatal(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, message, throwable); } @Override - public void fatal(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.FATAL, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void fatal(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.FATAL, null, messageSupplier, (Throwable) null); } @Override - public void fatal(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void fatal(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, messageSupplier, throwable); } @Override - public void fatal(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.FATAL, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void fatal(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.FATAL, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void fatal(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.FATAL, marker, message, paramSuppliers); } @Override - public void fatal(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void fatal(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void fatal(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.FATAL, null, message, paramSuppliers); } @Override - public void fatal(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.FATAL, marker, msgSupplier, (Throwable) null); + public void fatal(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.FATAL, marker, messageSupplier, (Throwable) null); } @Override - public void fatal(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, marker, msgSupplier, t); + public void fatal(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, marker, messageSupplier, throwable); } @Override - public void fatal(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.FATAL, null, msgSupplier, (Throwable) null); + public void fatal(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.FATAL, null, messageSupplier, (Throwable) null); } @Override - public void fatal(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.FATAL, null, msgSupplier, t); + public void fatal(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.FATAL, null, messageSupplier, throwable); } @Override @@ -1120,46 +1301,100 @@ public void fatal(final Marker marker, final String message, final Object p0, fi } @Override - public void fatal(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void fatal( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3); } @Override - public void fatal(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void fatal( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4); } @Override - public void fatal(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void fatal( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -1184,39 +1419,78 @@ public void fatal(final String message, final Object p0, final Object p1, final } @Override - public void fatal(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void fatal( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4); } @Override - public void fatal(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void fatal( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void fatal(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void fatal( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void fatal(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) { + public void fatal( + 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) { logIfEnabled(FQCN, Level.FATAL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -1226,19 +1500,24 @@ public MF getMessageFactory() { return (MF) messageFactory; } + @Override + public FlowMessageFactory getFlowMessageFactory() { + return flowMessageFactory; + } + @Override public String getName() { return name; } @Override - public void info(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.INFO, marker, msg, msg != null ? msg.getThrowable() : null); + public void info(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.INFO, marker, message, message != null ? message.getThrowable() : null); } @Override - public void info(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, msg, t); + public void info(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, message, throwable); } @Override @@ -1247,8 +1526,8 @@ public void info(final Marker marker, final CharSequence message) { } @Override - public void info(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, message, t); + public void info(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, message, throwable); } @Override @@ -1257,8 +1536,8 @@ public void info(final Marker marker, final Object message) { } @Override - public void info(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, message, t); + public void info(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, message, throwable); } @Override @@ -1272,18 +1551,18 @@ public void info(final Marker marker, final String message, final Object... para } @Override - public void info(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, message, t); + public void info(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, message, throwable); } @Override - public void info(final Message msg) { - logIfEnabled(FQCN, Level.INFO, null, msg, msg != null ? msg.getThrowable() : null); + public void info(final Message message) { + logIfEnabled(FQCN, Level.INFO, null, message, message != null ? message.getThrowable() : null); } @Override - public void info(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, msg, t); + public void info(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, message, throwable); } @Override @@ -1292,8 +1571,8 @@ public void info(final CharSequence message) { } @Override - public void info(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, message, t); + public void info(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, message, throwable); } @Override @@ -1302,8 +1581,8 @@ public void info(final Object message) { } @Override - public void info(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, message, t); + public void info(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, message, throwable); } @Override @@ -1317,58 +1596,64 @@ public void info(final String message, final Object... params) { } @Override - public void info(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, message, t); + public void info(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, message, throwable); } @Override - public void info(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.INFO, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void info(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.INFO, null, messageSupplier, (Throwable) null); } @Override - public void info(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void info(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, messageSupplier, throwable); } @Override - public void info(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.INFO, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void info(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.INFO, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void info(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.INFO, marker, message, paramSuppliers); } @Override - public void info(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void info(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void info(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.INFO, null, message, paramSuppliers); } @Override - public void info(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.INFO, marker, msgSupplier, (Throwable) null); + public void info(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.INFO, marker, messageSupplier, (Throwable) null); } @Override - public void info(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, marker, msgSupplier, t); + public void info(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, marker, messageSupplier, throwable); } @Override - public void info(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.INFO, null, msgSupplier, (Throwable) null); + public void info(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.INFO, null, messageSupplier, (Throwable) null); } @Override - public void info(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.INFO, null, msgSupplier, t); + public void info(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.INFO, null, messageSupplier, throwable); } @Override @@ -1387,46 +1672,100 @@ public void info(final Marker marker, final String message, final Object p0, fin } @Override - public void info(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void info( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3); } @Override - public void info(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void info( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4); } @Override - public void info(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void info( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -1451,41 +1790,78 @@ public void info(final String message, final Object p0, final Object p1, final O } @Override - public void info(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void info( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4); } @Override - public void info(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void info( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void info(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void info( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void info(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void info(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) { + public void info( + 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) { logIfEnabled(FQCN, Level.INFO, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -1560,13 +1936,13 @@ public boolean isWarnEnabled(final Marker marker) { } @Override - public void log(final Level level, final Marker marker, final Message msg) { - logIfEnabled(FQCN, level, marker, msg, msg != null ? msg.getThrowable() : null); + public void log(final Level level, final Marker marker, final Message message) { + logIfEnabled(FQCN, level, marker, message, message != null ? message.getThrowable() : null); } @Override - public void log(final Level level, final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, level, marker, msg, t); + public void log(final Level level, final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, level, marker, message, throwable); } @Override @@ -1575,9 +1951,9 @@ public void log(final Level level, final Marker marker, final CharSequence messa } @Override - public void log(final Level level, final Marker marker, final CharSequence message, final Throwable t) { - if (isEnabled(level, marker, message, t)) { - logMessage(FQCN, level, marker, message, t); + public void log(final Level level, final Marker marker, final CharSequence message, final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessage(FQCN, level, marker, message, throwable); } } @@ -1587,9 +1963,9 @@ public void log(final Level level, final Marker marker, final Object message) { } @Override - public void log(final Level level, final Marker marker, final Object message, final Throwable t) { - if (isEnabled(level, marker, message, t)) { - logMessage(FQCN, level, marker, message, t); + public void log(final Level level, final Marker marker, final Object message, final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessage(FQCN, level, marker, message, throwable); } } @@ -1604,18 +1980,18 @@ public void log(final Level level, final Marker marker, final String message, fi } @Override - public void log(final Level level, final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, level, marker, message, t); + public void log(final Level level, final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, level, marker, message, throwable); } @Override - public void log(final Level level, final Message msg) { - logIfEnabled(FQCN, level, null, msg, msg != null ? msg.getThrowable() : null); + public void log(final Level level, final Message message) { + logIfEnabled(FQCN, level, null, message, message != null ? message.getThrowable() : null); } @Override - public void log(final Level level, final Message msg, final Throwable t) { - logIfEnabled(FQCN, level, null, msg, t); + public void log(final Level level, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, level, null, message, throwable); } @Override @@ -1624,8 +2000,8 @@ public void log(final Level level, final CharSequence message) { } @Override - public void log(final Level level, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, level, null, message, t); + public void log(final Level level, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, level, null, message, throwable); } @Override @@ -1634,8 +2010,8 @@ public void log(final Level level, final Object message) { } @Override - public void log(final Level level, final Object message, final Throwable t) { - logIfEnabled(FQCN, level, null, message, t); + public void log(final Level level, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, level, null, message, throwable); } @Override @@ -1649,58 +2025,66 @@ public void log(final Level level, final String message, final Object... params) } @Override - public void log(final Level level, final String message, final Throwable t) { - logIfEnabled(FQCN, level, null, message, t); + public void log(final Level level, final String message, final Throwable throwable) { + logIfEnabled(FQCN, level, null, message, throwable); } @Override - public void log(final Level level, final Supplier msgSupplier) { - logIfEnabled(FQCN, level, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void log(final Level level, final Supplier messageSupplier) { + logIfEnabled(FQCN, level, null, messageSupplier, (Throwable) null); } @Override - public void log(final Level level, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, level, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void log(final Level level, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, level, null, messageSupplier, throwable); } @Override - public void log(final Level level, final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, level, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void log(final Level level, final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, level, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void log(final Level level, final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, level, marker, message, paramSuppliers); } @Override - public void log(final Level level, final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, level, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void log( + final Level level, final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, level, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void log(final Level level, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, level, null, message, paramSuppliers); } @Override - public void log(final Level level, final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, level, marker, msgSupplier, (Throwable) null); + public void log(final Level level, final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, level, marker, messageSupplier, (Throwable) null); } @Override - public void log(final Level level, final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, level, marker, msgSupplier, t); + public void log( + final Level level, final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, level, marker, messageSupplier, throwable); } @Override - public void log(final Level level, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, level, null, msgSupplier, (Throwable) null); + public void log(final Level level, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, level, null, messageSupplier, (Throwable) null); } @Override - public void log(final Level level, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, level, null, msgSupplier, t); + public void log(final Level level, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, level, null, messageSupplier, throwable); } @Override @@ -1714,53 +2098,118 @@ public void log(final Level level, final Marker marker, final String message, fi } @Override - public void log(final Level level, final Marker marker, final String message, final Object p0, final Object p1, + public void log( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, final Object p2) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2); } @Override - public void log(final Level level, final Marker marker, final String message, final Object p0, final Object p1, - final Object p2, final Object p3) { + public void log( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3); } @Override - public void log(final Level level, final Marker marker, final String message, final Object p0, final Object p1, - final Object p2, final Object p3, final Object p4) { + public void log( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4); } @Override - public void log(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) { + public void log( + 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) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void log(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) { + public void log( + 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) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void log(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) { + public void log( + 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) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void log(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) { + public void log( + 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) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void log(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) { + public void log( + 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) { logIfEnabled(FQCN, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -1780,83 +2229,161 @@ public void log(final Level level, final String message, final Object p0, final } @Override - public void log(final Level level, final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + public void log( + final Level level, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3); } @Override - public void log(final Level level, final String message, final Object p0, final Object p1, final Object p2, final Object p3, + public void log( + final Level level, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, final Object p4) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4); } @Override - public void log(final Level level, final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void log( + final Level level, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void log(final Level level, final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void log( + final Level level, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void log(final Level level, 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) { + public void log( + final Level level, + 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) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void log(final Level level, 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) { + public void log( + final Level level, + 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) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void log(final Level level, 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) { + public void log( + final Level level, + 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) { logIfEnabled(FQCN, level, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final Message msg, - final Throwable t) { - if (isEnabled(level, marker, msg, t)) { - logMessageSafely(fqcn, level, marker, msg, t); + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessageSafely(fqcn, level, marker, message, throwable); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, - final MessageSupplier msgSupplier, final Throwable t) { - if (isEnabled(level, marker, msgSupplier, t)) { - logMessage(fqcn, level, marker, msgSupplier, t); + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final MessageSupplier messageSupplier, + final Throwable throwable) { + if (isEnabled(level, marker, messageSupplier, throwable)) { + logMessage(fqcn, level, marker, messageSupplier, throwable); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final Object message, - final Throwable t) { - if (isEnabled(level, marker, message, t)) { - logMessage(fqcn, level, marker, message, t); + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final Object message, + final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessage(fqcn, level, marker, message, throwable); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final CharSequence message, - final Throwable t) { - if (isEnabled(level, marker, message, t)) { - logMessage(fqcn, level, marker, message, t); + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final CharSequence message, + final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessage(fqcn, level, marker, message, throwable); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final Supplier msgSupplier, - final Throwable t) { - if (isEnabled(level, marker, msgSupplier, t)) { - logMessage(fqcn, level, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final Supplier messageSupplier, + final Throwable throwable) { + if (isEnabled(level, marker, messageSupplier, throwable)) { + logMessage(fqcn, level, marker, messageSupplier, throwable); } } @@ -1868,7 +2395,12 @@ public void logIfEnabled(final String fqcn, final Level level, final Marker mark } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, + @SuppressWarnings("deprecation") + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, final Supplier... paramSuppliers) { if (isEnabled(level, marker, message)) { logMessage(fqcn, level, marker, message, paramSuppliers); @@ -1876,64 +2408,108 @@ public void logIfEnabled(final String fqcn, final Level level, final Marker mark } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object... params) { + public void logIfEnabled( + final String fqcn, final Level level, final Marker marker, final String message, final Object... params) { if (isEnabled(level, marker, message, params)) { logMessage(fqcn, level, marker, message, params); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0) { + public void logIfEnabled( + final String fqcn, final Level level, final Marker marker, final String message, final Object p0) { if (isEnabled(level, marker, message, p0)) { logMessage(fqcn, level, marker, message, p0); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1) { + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1) { if (isEnabled(level, marker, message, p0, p1)) { logMessage(fqcn, level, marker, message, p0, p1); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2) { + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { if (isEnabled(level, marker, message, p0, p1, p2)) { logMessage(fqcn, level, marker, message, p0, p1, p2); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2, final Object p3) { + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { if (isEnabled(level, marker, message, p0, p1, p2, p3)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4); } } @Override - public void logIfEnabled(final String fqcn, 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) { + public void logIfEnabled( + final String fqcn, + 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) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4, p5); } } @Override - public void logIfEnabled(final String fqcn, 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, + public void logIfEnabled( + final String fqcn, + 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) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4, p5, p6); @@ -1941,65 +2517,135 @@ public void logIfEnabled(final String fqcn, final Level level, final Marker mark } @Override - public void logIfEnabled(final String fqcn, 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) { + public void logIfEnabled( + final String fqcn, + 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) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } } @Override - public void logIfEnabled(final String fqcn, 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) { + public void logIfEnabled( + final String fqcn, + 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) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } } @Override - public void logIfEnabled(final String fqcn, 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) { + public void logIfEnabled( + final String fqcn, + 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) { if (isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { logMessage(fqcn, level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } } @Override - public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, - final Throwable t) { - if (isEnabled(level, marker, message, t)) { - logMessage(fqcn, level, marker, message, t); + public void logIfEnabled( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Throwable throwable) { + if (isEnabled(level, marker, message, throwable)) { + logMessage(fqcn, level, marker, message, throwable); } } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final CharSequence message, - final Throwable t) { - logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t); + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final CharSequence message, + final Throwable throwable) { + logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), throwable); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final Object message, - final Throwable t) { - logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t); + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final Object message, + final Throwable throwable) { + logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), throwable); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, - final MessageSupplier msgSupplier, final Throwable t) { - final Message message = LambdaUtil.get(msgSupplier); - logMessageSafely(fqcn, level, marker, message, (t == null && message != null) ? message.getThrowable() : t); + /** + * @since 2.4 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final MessageSupplier messageSupplier, + final Throwable throwable) { + final Message message = LambdaUtil.get(messageSupplier); + final Throwable effectiveThrowable = + (throwable == null && message != null) ? message.getThrowable() : throwable; + logMessageSafely(fqcn, level, marker, message, effectiveThrowable); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final Supplier msgSupplier, - final Throwable t) { - final Message message = LambdaUtil.getMessage(msgSupplier, messageFactory); - logMessageSafely(fqcn, level, marker, message, (t == null && message != null) ? message.getThrowable() : t); + /** + * @since 2.4 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final Supplier messageSupplier, + final Throwable throwable) { + final Message message = LambdaUtil.getMessage(messageSupplier, messageFactory); + final Throwable effectiveThrowable = + (throwable == null && message != null) ? message.getThrowable() : throwable; + logMessageSafely(fqcn, level, marker, message, effectiveThrowable); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Throwable t) { - logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t); + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Throwable throwable) { + logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), throwable); } protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) { @@ -2007,122 +2653,273 @@ protected void logMessage(final String fqcn, final Level level, final Marker mar logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object... params) { + protected void logMessage( + final String fqcn, final Level level, final Marker marker, final String message, final Object... params) { final Message msg = messageFactory.newMessage(message, params); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, final Level level, final Marker marker, final String message, final Object p0) { final Message msg = messageFactory.newMessage(message, p0); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1) { final Message msg = messageFactory.newMessage(message, p0, p1); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { final Message msg = messageFactory.newMessage(message, p0, p1, p2); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2, final Object p3) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, - final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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 Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, 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) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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 Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, 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, + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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 Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, 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) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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 Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, 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) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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 Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, 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) { + /** + * @since 2.6 + */ + protected void logMessage( + final String fqcn, + 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) { final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } - protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message, + /** + * @since 2.4 + */ + protected void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final String message, final Supplier... paramSuppliers) { final Message msg = messageFactory.newMessage(message, LambdaUtil.getAll(paramSuppliers)); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } + @Override + 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); + } + } + + /** + * @since 2.12.1 + */ + 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)) { - final Message msg = new StringFormattedMessage(format, params); - logMessageSafely(FQCN, level, marker, msg, msg.getThrowable()); + final Message message = new StringFormattedMessage(format, params); + logMessageSafely(FQCN, level, marker, message, message.getThrowable()); } } @Override public void printf(final Level level, final String format, final Object... params) { if (isEnabled(level, null, format, params)) { - final Message msg = new StringFormattedMessage(format, params); - logMessageSafely(FQCN, level, null, msg, msg.getThrowable()); + final Message message = new StringFormattedMessage(format, params); + logMessageSafely(FQCN, level, null, message, message.getThrowable()); } } @PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 30 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! - private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message msg, + private void logMessageSafely( + final String fqcn, + final Level level, + final Marker marker, + final Message message, final Throwable throwable) { try { - logMessageTrackRecursion(fqcn, level, marker, msg, throwable); + logMessageTrackRecursion(fqcn, level, marker, message, throwable); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) - ReusableMessageFactory.release(msg); + ReusableMessageFactory.release(message); } } @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, - final Marker marker, - final Message msg, - final Throwable throwable) { + private void logMessageTrackRecursion( + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable throwable) { try { incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031 - tryLogMessage(fqcn, level, marker, msg, throwable); + tryLogMessage(fqcn, getLocation(fqcn), level, marker, message, throwable); } finally { decrementRecursionDepth(); } @@ -2140,11 +2937,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]); + final int newDepth = --getRecursionDepthHolder()[0]; + if (newDepth < 0) { + throw new IllegalStateException("Recursion depth became negative: " + newDepth); } } @@ -2153,6 +2950,7 @@ private static void decrementRecursionDepth() { * one if a single logging call without nested logging calls has been made, or more depending on the level of * nesting. * @return the depth of the nested logging calls in the current Thread + * @since 2.10.0 */ public static int getRecursionDepth() { return getRecursionDepthHolder()[0]; @@ -2161,45 +2959,52 @@ public static int getRecursionDepth() { @PerformanceSensitive // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! - private void tryLogMessage(final String fqcn, - final Level level, - final Marker marker, - final Message msg, - final Throwable throwable) { + private void tryLogMessage( + final String fqcn, + final StackTraceElement location, + final Level level, + final Marker marker, + 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 - public T throwing(final T t) { - return throwing(FQCN, Level.ERROR, t); + public T throwing(final T throwable) { + return throwing(FQCN, Level.ERROR, throwable); } @Override - public T throwing(final Level level, final T t) { - return throwing(FQCN, level, t); + public T throwing(final Level level, final T throwable) { + return throwing(FQCN, level, throwable); } /** @@ -2208,28 +3013,28 @@ public T throwing(final Level level, final T t) { * @param the type of the Throwable. * @param fqcn the fully qualified class name of this Logger implementation. * @param level The logging Level. - * @param t The Throwable. + * @param throwable The Throwable. * @return the Throwable. */ - protected T throwing(final String fqcn, final Level level, final T t) { + protected T throwing(final String fqcn, final Level level, final T throwable) { if (isEnabled(level, THROWING_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, level, THROWING_MARKER, throwingMsg(t), t); + logMessageSafely(fqcn, level, THROWING_MARKER, throwingMsg(throwable), throwable); } - return t; + return throwable; } - protected Message throwingMsg(final Throwable t) { + protected Message throwingMsg(final Throwable throwable) { return messageFactory.newMessage(THROWING); } @Override - public void trace(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.TRACE, marker, msg, msg != null ? msg.getThrowable() : null); + public void trace(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.TRACE, marker, message, message != null ? message.getThrowable() : null); } @Override - public void trace(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, msg, t); + public void trace(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, message, throwable); } @Override @@ -2238,8 +3043,8 @@ public void trace(final Marker marker, final CharSequence message) { } @Override - public void trace(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, message, t); + public void trace(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, message, throwable); } @Override @@ -2248,8 +3053,8 @@ public void trace(final Marker marker, final Object message) { } @Override - public void trace(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, message, t); + public void trace(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, message, throwable); } @Override @@ -2263,18 +3068,18 @@ public void trace(final Marker marker, final String message, final Object... par } @Override - public void trace(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, message, t); + public void trace(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, message, throwable); } @Override - public void trace(final Message msg) { - logIfEnabled(FQCN, Level.TRACE, null, msg, msg != null ? msg.getThrowable() : null); + public void trace(final Message message) { + logIfEnabled(FQCN, Level.TRACE, null, message, message != null ? message.getThrowable() : null); } @Override - public void trace(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, msg, t); + public void trace(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, message, throwable); } @Override @@ -2283,8 +3088,8 @@ public void trace(final CharSequence message) { } @Override - public void trace(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, message, t); + public void trace(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, message, throwable); } @Override @@ -2293,8 +3098,8 @@ public void trace(final Object message) { } @Override - public void trace(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, message, t); + public void trace(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, message, throwable); } @Override @@ -2308,58 +3113,64 @@ public void trace(final String message, final Object... params) { } @Override - public void trace(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, message, t); + public void trace(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, message, throwable); } @Override - public void trace(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.TRACE, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void trace(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.TRACE, null, messageSupplier, (Throwable) null); } @Override - public void trace(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void trace(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, messageSupplier, throwable); } @Override - public void trace(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.TRACE, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void trace(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.TRACE, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void trace(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.TRACE, marker, message, paramSuppliers); } @Override - public void trace(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void trace(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void trace(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.TRACE, null, message, paramSuppliers); } @Override - public void trace(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.TRACE, marker, msgSupplier, (Throwable) null); + public void trace(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.TRACE, marker, messageSupplier, (Throwable) null); } @Override - public void trace(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, marker, msgSupplier, t); + public void trace(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, marker, messageSupplier, throwable); } @Override - public void trace(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.TRACE, null, msgSupplier, (Throwable) null); + public void trace(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.TRACE, null, messageSupplier, (Throwable) null); } @Override - public void trace(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.TRACE, null, msgSupplier, t); + public void trace(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.TRACE, null, messageSupplier, throwable); } @Override @@ -2378,46 +3189,100 @@ public void trace(final Marker marker, final String message, final Object p0, fi } @Override - public void trace(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void trace( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3); } @Override - public void trace(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void trace( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4); } @Override - public void trace(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void trace( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -2442,38 +3307,78 @@ public void trace(final String message, final Object p0, final Object p1, final } @Override - public void trace(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void trace( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4); } @Override - public void trace(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void trace( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void trace(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void trace( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void trace(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) { + public void trace( + 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) { logIfEnabled(FQCN, Level.TRACE, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -2488,11 +3393,13 @@ public EntryMessage traceEntry(final String format, final Object... params) { } @Override + @SuppressWarnings("deprecation") public EntryMessage traceEntry(final Supplier... paramSuppliers) { return enter(FQCN, null, paramSuppliers); } @Override + @SuppressWarnings("deprecation") public EntryMessage traceEntry(final String format, final Supplier... paramSuppliers) { return enter(FQCN, format, paramSuppliers); } @@ -2519,7 +3426,8 @@ public R traceExit(final String format, final R result) { @Override public void traceExit(final EntryMessage message) { - // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled(). + // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out + // calling isEnabled(). if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) { logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(message), null); } @@ -2527,7 +3435,8 @@ public void traceExit(final EntryMessage message) { @Override public R traceExit(final EntryMessage message, final R result) { - // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled(). + // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out + // calling isEnabled(). if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) { logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null); } @@ -2536,7 +3445,8 @@ public R traceExit(final EntryMessage message, final R result) { @Override public R traceExit(final Message message, final R result) { - // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled(). + // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out + // calling isEnabled(). if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) { logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null); } @@ -2544,13 +3454,13 @@ public R traceExit(final Message message, final R result) { } @Override - public void warn(final Marker marker, final Message msg) { - logIfEnabled(FQCN, Level.WARN, marker, msg, msg != null ? msg.getThrowable() : null); + public void warn(final Marker marker, final Message message) { + logIfEnabled(FQCN, Level.WARN, marker, message, message != null ? message.getThrowable() : null); } @Override - public void warn(final Marker marker, final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, msg, t); + public void warn(final Marker marker, final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, message, throwable); } @Override @@ -2559,8 +3469,8 @@ public void warn(final Marker marker, final CharSequence message) { } @Override - public void warn(final Marker marker, final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, message, t); + public void warn(final Marker marker, final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, message, throwable); } @Override @@ -2569,8 +3479,8 @@ public void warn(final Marker marker, final Object message) { } @Override - public void warn(final Marker marker, final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, message, t); + public void warn(final Marker marker, final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, message, throwable); } @Override @@ -2584,18 +3494,18 @@ public void warn(final Marker marker, final String message, final Object... para } @Override - public void warn(final Marker marker, final String message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, message, t); + public void warn(final Marker marker, final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, message, throwable); } @Override - public void warn(final Message msg) { - logIfEnabled(FQCN, Level.WARN, null, msg, msg != null ? msg.getThrowable() : null); + public void warn(final Message message) { + logIfEnabled(FQCN, Level.WARN, null, message, message != null ? message.getThrowable() : null); } @Override - public void warn(final Message msg, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, msg, t); + public void warn(final Message message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, message, throwable); } @Override @@ -2604,8 +3514,8 @@ public void warn(final CharSequence message) { } @Override - public void warn(final CharSequence message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, message, t); + public void warn(final CharSequence message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, message, throwable); } @Override @@ -2614,8 +3524,8 @@ public void warn(final Object message) { } @Override - public void warn(final Object message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, message, t); + public void warn(final Object message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, message, throwable); } @Override @@ -2629,58 +3539,64 @@ public void warn(final String message, final Object... params) { } @Override - public void warn(final String message, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, message, t); + public void warn(final String message, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, message, throwable); } @Override - public void warn(final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.WARN, null, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void warn(final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.WARN, null, messageSupplier, (Throwable) null); } @Override - public void warn(final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, msgSupplier, t); + @SuppressWarnings("deprecation") + public void warn(final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, messageSupplier, throwable); } @Override - public void warn(final Marker marker, final Supplier msgSupplier) { - logIfEnabled(FQCN, Level.WARN, marker, msgSupplier, (Throwable) null); + @SuppressWarnings("deprecation") + public void warn(final Marker marker, final Supplier messageSupplier) { + logIfEnabled(FQCN, Level.WARN, marker, messageSupplier, (Throwable) null); } @Override + @SuppressWarnings("deprecation") public void warn(final Marker marker, final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.WARN, marker, message, paramSuppliers); } @Override - public void warn(final Marker marker, final Supplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, msgSupplier, t); + @SuppressWarnings("deprecation") + public void warn(final Marker marker, final Supplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, messageSupplier, throwable); } @Override + @SuppressWarnings("deprecation") public void warn(final String message, final Supplier... paramSuppliers) { logIfEnabled(FQCN, Level.WARN, null, message, paramSuppliers); } @Override - public void warn(final Marker marker, final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.WARN, marker, msgSupplier, (Throwable) null); + public void warn(final Marker marker, final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.WARN, marker, messageSupplier, (Throwable) null); } @Override - public void warn(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, marker, msgSupplier, t); + public void warn(final Marker marker, final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, marker, messageSupplier, throwable); } @Override - public void warn(final MessageSupplier msgSupplier) { - logIfEnabled(FQCN, Level.WARN, null, msgSupplier, (Throwable) null); + public void warn(final MessageSupplier messageSupplier) { + logIfEnabled(FQCN, Level.WARN, null, messageSupplier, (Throwable) null); } @Override - public void warn(final MessageSupplier msgSupplier, final Throwable t) { - logIfEnabled(FQCN, Level.WARN, null, msgSupplier, t); + public void warn(final MessageSupplier messageSupplier, final Throwable throwable) { + logIfEnabled(FQCN, Level.WARN, null, messageSupplier, throwable); } @Override @@ -2699,45 +3615,100 @@ public void warn(final Marker marker, final String message, final Object p0, fin } @Override - public void warn(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + public void warn( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, final Object p3) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3); } @Override - public void warn(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4) { + public void warn( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4); } @Override - public void warn(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, - final Object p3, final Object p4, final Object p5) { + public void warn( + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4, p5); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -2762,39 +3733,183 @@ public void warn(final String message, final Object p0, final Object p1, final O } @Override - public void warn(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { + public void warn( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4); } @Override - public void warn(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { + public void warn( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5); } @Override - public void warn(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { + public void warn( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6, p7); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @Override - public void warn(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) { + public void warn( + 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) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } + + /** + * @since 2.12.1 + */ + 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() { + return getLogBuilder(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); + } + return LogBuilder.NOOP; + } + + /** + * Returns a log builder that logs at the specified level. + * + * @since 2.20.0 + */ + protected LogBuilder getLogBuilder(final Level level) { + if (Constants.ENABLE_THREADLOCALS) { + final DefaultLogBuilder builder = logBuilder.get(); + if (!builder.isInUse()) { + return builder.reset(this, level); + } + } + return new DefaultLogBuilder(this, level); + } } 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..bd38b48420b 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 @@ -1,45 +1,45 @@ /* * 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.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; import java.util.concurrent.locks.ReentrantReadWriteLock; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.util.LoaderUtil; /** * 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); + private final ReadWriteLock lock = new ReentrantReadWriteLock(true); @Override public L getLogger(final String name) { @@ -53,6 +53,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. * @@ -61,29 +66,41 @@ public L getLogger(final String name) { */ public ConcurrentMap getLoggersInContext(final LoggerContext context) { ConcurrentMap loggers; - lock.readLock ().lock (); + lock.readLock().lock(); try { - loggers = registry.get (context); + loggers = registry.get(context); } finally { - lock.readLock ().unlock (); + lock.readLock().unlock(); } if (loggers != null) { return loggers; } - lock.writeLock ().lock (); + lock.writeLock().lock(); try { - loggers = registry.get (context); + loggers = registry.get(context); if (loggers == null) { - loggers = new ConcurrentHashMap<> (); - registry.put (context, loggers); + loggers = new ConcurrentHashMap<>(); + registry.put(context, loggers); + if (context instanceof LoggerContextShutdownEnabled) { + ((LoggerContextShutdownEnabled) context).addShutdownListener(this); + } } return loggers; } finally { - lock.writeLock ().unlock (); + lock.writeLock().unlock(); } } + /** + * For unit testing. Consider to be private. + * + * @since 2.12.1 + */ + public Set getLoggerContexts() { + return new HashSet<>(registry.keySet()); + } + /** * Creates a new named logger for a given {@link LoggerContext}. * @@ -123,11 +140,11 @@ protected LoggerContext getContext(final Class callerClass) { @Override public void close() { - lock.writeLock ().lock (); + lock.writeLock().lock(); try { registry.clear(); } finally { - lock.writeLock ().unlock (); + lock.writeLock().unlock(); } } } 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..b5d97454b20 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -30,10 +30,9 @@ public interface CleanableThreadContextMap extends ThreadContextMap2 { * *

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/CopyOnWrite.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWrite.java index a47eafefa1d..ff958f1516f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWrite.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWrite.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -21,6 +21,7 @@ * * @see ReadOnlyThreadContextMap#getReadOnlyContextData() * @since 2.7 + * @deprecated Since 2.24.0 no class implements this. */ -public interface CopyOnWrite { -} +@Deprecated +public interface CopyOnWrite {} 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 deleted file mode 100644 index 862246e3fa4..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java +++ /dev/null @@ -1,278 +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.Collections; -import java.util.HashMap; -import java.util.Map; - -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; - -/** - * {@code SortedArrayStringMap}-based implementation of the {@code ThreadContextMap} interface that creates a copy of - * the data structure on every modification. Any particular instance of the data structure is a snapshot of the - * ThreadContext at some point in time and can safely be passed off to other threads. Since it is - * expected that the Map will be passed to many more log events than the number of keys it contains the performance - * should be much better than if the Map was copied for each event. - * - * @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"; - - 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; - - public CopyOnWriteSortedArrayThreadContextMap() { - this.localMap = createThreadLocalMap(); - } - - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - private ThreadLocal createThreadLocalMap() { - if (inheritableMap) { - return new InheritableThreadLocal() { - @Override - protected StringMap childValue(final StringMap parentValue) { - if (parentValue == null) { - return null; - } - StringMap stringMap = createStringMap(parentValue); - stringMap.freeze(); - return stringMap; - } - }; - } - // if not inheritable, return plain ThreadLocal with null as initial value - return new ThreadLocal<>(); - } - - /** - * 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 - */ - protected StringMap createStringMap() { - return new SortedArrayStringMap(initialCapacity); - } - - /** - * Returns an implementation of the {@code StringMap} used to back this thread context map, pre-populated - * with the contents of the specified context data. - *

- * Subclasses may override. - *

- * @param original the key-value pairs to initialize the returned context data with - * @return an implementation of the {@code StringMap} used to back this thread context map - */ - protected StringMap createStringMap(final ReadOnlyStringMap original) { - return new SortedArrayStringMap(original); - } - - @Override - public void put(final String key, final String value) { - putValue(key, value); - } - - @Override - public void putValue(final String key, final Object value) { - StringMap map = localMap.get(); - map = map == null ? createStringMap() : createStringMap(map); - map.putValue(key, value); - map.freeze(); - localMap.set(map); - } - - @Override - public void putAll(final Map values) { - if (values == null || values.isEmpty()) { - return; - } - StringMap map = localMap.get(); - map = map == null ? createStringMap() : createStringMap(map); - for (final Map.Entry entry : values.entrySet()) { - map.putValue(entry.getKey(), entry.getValue()); - } - map.freeze(); - localMap.set(map); - } - - @Override - public void putAllValues(final Map values) { - if (values == null || values.isEmpty()) { - return; - } - StringMap map = localMap.get(); - map = map == null ? createStringMap() : createStringMap(map); - for (final Map.Entry entry : values.entrySet()) { - map.putValue(entry.getKey(), entry.getValue()); - } - map.freeze(); - localMap.set(map); - } - - @Override - public String get(final String key) { - return (String) getValue(key); - } - - @Override - public V getValue(final String key) { - final StringMap map = localMap.get(); - return map == null ? null : map.getValue(key); - } - - @Override - public void remove(final String key) { - final StringMap map = localMap.get(); - if (map != null) { - final StringMap copy = createStringMap(map); - copy.remove(key); - copy.freeze(); - localMap.set(copy); - } - } - - @Override - public void removeAll(final Iterable keys) { - final StringMap map = localMap.get(); - if (map != null) { - final StringMap copy = createStringMap(map); - for (final String key : keys) { - copy.remove(key); - } - copy.freeze(); - localMap.set(copy); - } - } - - @Override - public void clear() { - localMap.remove(); - } - - @Override - public boolean containsKey(final String key) { - final StringMap map = localMap.get(); - return map != null && map.containsKey(key); - } - - @Override - public Map getCopy() { - final StringMap map = localMap.get(); - return map == null ? new HashMap() : map.toMap(); - } - - /** - * {@inheritDoc} - */ - @Override - public StringMap getReadOnlyContextData() { - final StringMap map = localMap.get(); - return map == null ? EMPTY_CONTEXT_DATA : map; - } - - @Override - public Map getImmutableMapOrNull() { - final StringMap map = localMap.get(); - return map == null ? null : Collections.unmodifiableMap(map.toMap()); - } - - @Override - public boolean isEmpty() { - final StringMap map = localMap.get(); - return map == null || map.size() == 0; - } - - @Override - public String toString() { - final StringMap map = localMap.get(); - return map == null ? "{}" : map.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - final StringMap map = this.localMap.get(); - result = prime * result + ((map == null) ? 0 : map.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof ThreadContextMap)) { - return false; - } - 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; - } -} 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..62752428f50 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 @@ -1,137 +1,115 @@ /* * 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.logging.log4j.spi; -import java.util.Collections; +import static org.apache.logging.log4j.internal.map.UnmodifiableArrayBackedMap.getMap; + 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.PropertiesUtil; +import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.TriConsumer; /** - * 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 - * expected that the Map will be passed to many more log events than the number of keys it contains the performance - * should be much better than if the Map was copied for each event. + * The default implementation of {@link ThreadContextMap} + *

+ * An instance of UnmodifiableArrayBackedMap can be represented as a single {@code Object[]}, which can safely + * be stored on the {@code ThreadLocal} with no fear of classloader-related memory leaks. + *

+ *

+ * Performance of the underlying {@link org.apache.logging.log4j.internal.map.UnmodifiableArrayBackedMap} exceeds + * {@link HashMap} in all supported operations other than {@code get()}. Note that {@code get()} performance scales + * linearly with the current map size, and callers are advised to minimize this work. + *

*/ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap { - private static final long serialVersionUID = 8218007901108944053L; + private static final long serialVersionUID = -2635197170958057849L; /** - * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain + * 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"; - 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) { - if (inheritableMap) { - return new InheritableThreadLocal>() { - @Override - protected Map childValue(final Map parentValue) { - return parentValue != null && isMapEnabled // - ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // - : null; - } - }; - } - // if not inheritable, return plain ThreadLocal with null as initial value - return new ThreadLocal<>(); - } + private ThreadLocal localState; - static void init() { - inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP); - } - public DefaultThreadContextMap() { - this(true); + this(PropertiesUtil.getProperties()); } - public DefaultThreadContextMap(final boolean useMap) { - this.useMap = useMap; - this.localMap = createThreadLocalMap(useMap); + /** + * @deprecated Since 2.24.0. Use {@link NoOpThreadContextMap} for a no-op implementation. + */ + @Deprecated + public DefaultThreadContextMap(final boolean ignored) { + this(PropertiesUtil.getProperties()); + } + + DefaultThreadContextMap(final PropertiesUtil properties) { + localState = properties.getBooleanProperty(INHERITABLE_MAP) + ? new InheritableThreadLocal() { + @Override + protected Object[] childValue(final Object[] parentValue) { + return parentValue; + } + } + : new ThreadLocal<>(); } @Override public void put(final String key, final String value) { - if (!useMap) { - return; - } - Map map = localMap.get(); - map = map == null ? new HashMap(1) : new HashMap<>(map); - map.put(key, value); - localMap.set(Collections.unmodifiableMap(map)); + final Object[] state = localState.get(); + localState.set(getMap(state).copyAndPut(key, value).getBackingArray()); } 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()); - } - localMap.set(Collections.unmodifiableMap(map)); + final Object[] state = localState.get(); + localState.set(getMap(state).copyAndPutAll(m).getBackingArray()); } @Override public String get(final String key) { - final Map map = localMap.get(); - return map == null ? null : map.get(key); + final Object[] state = localState.get(); + return state == null ? null : getMap(state).get(key); } @Override public void remove(final String key) { - final Map map = localMap.get(); - if (map != null) { - final Map copy = new HashMap<>(map); - copy.remove(key); - localMap.set(Collections.unmodifiableMap(copy)); + final Object[] state = localState.get(); + if (state != null) { + localState.set(getMap(state).copyAndRemove(key).getBackingArray()); } } + /** + * @since 2.8 + */ public void removeAll(final Iterable keys) { - final Map map = localMap.get(); - if (map != null) { - final Map copy = new HashMap<>(map); - for (final String key : keys) { - copy.remove(key); - } - localMap.set(Collections.unmodifiableMap(copy)); + final Object[] state = localState.get(); + if (state != null) { + localState.set(getMap(state).copyAndRemoveAll(keys).getBackingArray()); } } @Override public void clear() { - localMap.remove(); + localState.remove(); } @Override @@ -141,111 +119,93 @@ public Map toMap() { @Override public boolean containsKey(final String key) { - final Map map = localMap.get(); - return map != null && map.containsKey(key); + final Object[] state = localState.get(); + return state != null && getMap(state).containsKey(key); } @Override public void forEach(final BiConsumer action) { - final Map map = localMap.get(); - if (map == null) { + final Object[] state = localState.get(); + if (state == null) { return; } - 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(); - action.accept(entry.getKey(), value); - } + getMap(state).forEach(action); } @Override public void forEach(final TriConsumer action, final S state) { - final Map map = localMap.get(); - if (map == null) { + final Object[] localState = this.localState.get(); + if (localState == null) { return; } - 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(); - action.accept(entry.getKey(), value, state); - } + getMap(localState).forEach(action, state); } @SuppressWarnings("unchecked") @Override public V getValue(final String key) { - final Map map = localMap.get(); - return (V) (map == null ? null : map.get(key)); + return (V) get(key); } @Override public Map getCopy() { - final Map map = localMap.get(); - return map == null ? new HashMap() : new HashMap<>(map); + final Object[] state = localState.get(); + if (state == null) { + return new HashMap<>(0); + } + return new HashMap<>(getMap(state)); } @Override public Map getImmutableMapOrNull() { - return localMap.get(); + final Object[] state = localState.get(); + return (state == null ? null : getMap(state)); } @Override public boolean isEmpty() { - final Map map = localMap.get(); - return map == null || map.size() == 0; + return size() == 0; } @Override public int size() { - final Map map = localMap.get(); - return map == null ? 0 : map.size(); + final Object[] state = localState.get(); + return getMap(state).size(); } @Override public String toString() { - final Map map = localMap.get(); - return map == null ? "{}" : map.toString(); + final Object[] state = localState.get(); + return state == null ? "{}" : getMap(state).toString(); } @Override public int hashCode() { - final int prime = 31; - int result = 1; - final Map map = this.localMap.get(); - result = prime * result + ((map == null) ? 0 : map.hashCode()); - result = prime * result + Boolean.valueOf(this.useMap).hashCode(); - return result; + return toMap().hashCode(); } @Override - public boolean equals(final Object obj) { + public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } - if (obj instanceof DefaultThreadContextMap) { - final DefaultThreadContextMap other = (DefaultThreadContextMap) obj; - if (this.useMap != other.useMap) { + if (obj instanceof ReadOnlyStringMap) { + if (size() != ((ReadOnlyStringMap) obj).size()) { return false; } + + // Convert to maps and compare + obj = ((ReadOnlyStringMap) obj).toMap(); } if (!(obj instanceof ThreadContextMap)) { return false; } final ThreadContextMap other = (ThreadContextMap) obj; - final Map map = this.localMap.get(); + final Map map = getMap(localState.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..5ed3fb41eaa 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; - import org.apache.logging.log4j.ThreadContext.ContextStack; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.StringBuilders; @@ -32,12 +31,22 @@ */ 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<>(); private final boolean useStack; + public DefaultThreadContextStack() { + this(true); + } + + /** + * @deprecated since 2.24.0 without a replacement. + */ + @Deprecated public DefaultThreadContextStack(final boolean useStack) { this.useStack = useStack; } @@ -95,7 +104,7 @@ public boolean contains(final Object o) { public boolean containsAll(final Collection objects) { if (objects.isEmpty()) { // quick check before accessing the ThreadLocal return true; // looks counter-intuitive, but see - // j.u.AbstractCollection + // j.u.AbstractCollection } final MutableThreadContextStack values = STACK.get(); return values != null && values.containsAll(objects); @@ -170,7 +179,7 @@ public Iterator iterator() { @Override public String peek() { final MutableThreadContextStack values = STACK.get(); - if (values == null || values.size() == 0) { + if (values == null || values.isEmpty()) { return Strings.EMPTY; } return values.peek(); @@ -182,7 +191,7 @@ public String pop() { return Strings.EMPTY; } final MutableThreadContextStack values = STACK.get(); - if (values == null || values.size() == 0) { + if (values == null || values.isEmpty()) { // Like version 1.2 return Strings.EMPTY; } @@ -207,7 +216,7 @@ public boolean remove(final Object o) { return false; } final MutableThreadContextStack values = STACK.get(); - if (values == null || values.size() == 0) { + if (values == null || values.isEmpty()) { return false; } final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy(); @@ -259,9 +268,9 @@ public int size() { public Object[] toArray() { final MutableThreadContextStack result = STACK.get(); if (result == null) { - return new String[0]; + return Strings.EMPTY_ARRAY; } - 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..e344458afe6 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -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,18 +41,19 @@ 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. * @param message The message. * @param t A Throwable. * @return True if logging is enabled, false otherwise. + * @since 2.6 */ 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 +64,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 +75,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 +85,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,18 +96,19 @@ 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. * @param message The message. * @param p0 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ 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. @@ -114,11 +116,12 @@ public interface ExtendedLogger extends Logger { * @param p0 the message parameters * @param p1 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ 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. @@ -127,11 +130,12 @@ public interface ExtendedLogger extends Logger { * @param p1 the message parameters * @param p2 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ 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. @@ -141,11 +145,12 @@ public interface ExtendedLogger extends Logger { * @param p2 the message parameters * @param p3 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ 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. @@ -156,12 +161,13 @@ public interface ExtendedLogger extends Logger { * @param p3 the message parameters * @param p4 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ - boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4); + boolean isEnabled( + Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, 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. @@ -173,9 +179,18 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p4 the message parameters * @param p5 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ - boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5); + boolean isEnabled( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5); /** * Determines if logging is enabled. @@ -191,12 +206,22 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p5 the message parameters * @param p6 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ - boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6); + boolean isEnabled( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + 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. @@ -210,12 +235,23 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p6 the message parameters * @param p7 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ - boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6, Object p7); + boolean isEnabled( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + 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. @@ -230,12 +266,24 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p7 the message parameters * @param p8 the message parameters * @return True if logging is enabled, false otherwise. + * @since 2.6 */ - boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6, Object p7, Object p8); + boolean isEnabled( + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + 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. @@ -251,9 +299,22 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p8 the message parameters * @param p9 the message parameters * @return True if logging is enabled, false otherwise. - */ - boolean isEnabled(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); + * @since 2.6 + */ + boolean isEnabled( + 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); /** * Logs a message if the specified level is active. @@ -276,6 +337,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param marker A Marker or null. * @param message The CharSequence message. * @param t the exception to log, including its stack trace. + * @since 2.6 */ void logIfEnabled(String fqcn, Level level, Marker marker, CharSequence message, Throwable t); @@ -335,6 +397,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param marker A Marker or null. * @param message The message format. * @param p0 the message parameters + * @since 2.6 */ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0); @@ -348,6 +411,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param message The message format. * @param p0 the message parameters * @param p1 the message parameters + * @since 2.6 */ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1); @@ -362,6 +426,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p0 the message parameters * @param p1 the message parameters * @param p2 the message parameters + * @since 2.6 */ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2); @@ -377,9 +442,10 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object * @param p1 the message parameters * @param p2 the message parameters * @param p3 the message parameters + * @since 2.6 */ - void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, - Object p3); + void logIfEnabled( + String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3); /** * Logs a message if the specified level is active. @@ -394,9 +460,18 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p2 the message parameters * @param p3 the message parameters * @param p4 the message parameters - */ - void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, - Object p3, Object p4); + * @since 2.6 + */ + void logIfEnabled( + String fqcn, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4); /** * Logs a message if the specified level is active. @@ -412,9 +487,19 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p3 the message parameters * @param p4 the message parameters * @param p5 the message parameters + * @since 2.6 */ - void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, - Object p3, Object p4, Object p5); + void logIfEnabled( + String fqcn, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5); /** * Logs a message if the specified level is active. @@ -431,9 +516,20 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p4 the message parameters * @param p5 the message parameters * @param p6 the message parameters - */ - void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, - Object p3, Object p4, Object p5, Object p6); + * @since 2.6 + */ + void logIfEnabled( + String fqcn, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6); /** * Logs a message if the specified level is active. @@ -451,9 +547,21 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p5 the message parameters * @param p6 the message parameters * @param p7 the message parameters - */ - 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); + * @since 2.6 + */ + 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); /** * Logs a message if the specified level is active. @@ -472,9 +580,22 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p6 the message parameters * @param p7 the message parameters * @param p8 the message parameters - */ - 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); + * @since 2.6 + */ + 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); /** * Logs a message if the specified level is active. @@ -494,21 +615,35 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param p7 the message parameters * @param p8 the message parameters * @param p9 the message parameters + * @since 2.6 + */ + 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); + + /** + * 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 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. - */ void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable t); /** @@ -520,6 +655,7 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param marker A Marker or null. * @param msgSupplier A function, which when called, produces the desired log message. * @param t the exception to log, including its stack trace. + * @since 2.4 */ void logIfEnabled(String fqcn, Level level, Marker marker, MessageSupplier msgSupplier, Throwable t); @@ -532,7 +668,9 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param marker A Marker or null. * @param message The message format. * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since 2.4 */ + @SuppressWarnings("deprecation") void logIfEnabled(String fqcn, Level level, Marker marker, String message, Supplier... paramSuppliers); /** @@ -544,7 +682,8 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec * @param marker A Marker or null. * @param msgSupplier A function, which when called, produces the desired log message. * @param t the exception to log, including its stack trace. + * @since 2.4 */ + @SuppressWarnings("deprecation") void logIfEnabled(String fqcn, Level level, Marker marker, Supplier msgSupplier, 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..c8ad2e23f76 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -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. @@ -125,65 +126,124 @@ public boolean isEnabled(final Level level, final Marker marker, final String me } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1) { + public boolean isEnabled( + final Level level, final Marker marker, final String message, final Object p0, final Object p1) { return logger.isEnabled(level, marker, message, p0, p1); } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { return logger.isEnabled(level, marker, message, p0, p1, p2); } @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) { + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3); } @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, + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4); } @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) { + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5); } @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) { + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6); } @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, + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @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) { + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @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) { + 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 logger.isEnabled(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -212,8 +272,13 @@ public boolean isEnabled(final Level level, final Marker marker, final String me * @param t A Throwable or null. */ @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); + public void logMessage( + final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { + if (logger instanceof LocationAwareLogger && requiresLocation()) { + ((LocationAwareLogger) 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/LocationAwareLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LocationAwareLogger.java new file mode 100644 index 00000000000..c23a5610be9 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LocationAwareLogger.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.spi; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; + +/** + * Logger that accepts the location of the caller. + * + * @since 2.12.1 + */ +public interface LocationAwareLogger { + void logMessage( + final Level level, + final Marker marker, + final String fqcn, + final StackTraceElement location, + final Message message, + final Throwable throwable); +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerAdapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerAdapter.java index 6bac9bd54ca..d4dcfb35158 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerAdapter.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerAdapter.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; 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..d5a9119a1e3 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 @@ -1,22 +1,24 @@ /* * 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.logging.log4j.spi; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.MessageFactory; +import org.jspecify.annotations.Nullable; /** * Anchor point for logging implementations. @@ -24,49 +26,142 @@ public interface LoggerContext { /** - * An anchor for some other context, such as a ClassLoader or ServletContext. + * Empty array. + * @since 2.17.2 + */ + 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(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(Class cls, @Nullable 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. * @return The logger with the specified name. */ - ExtendedLogger getLogger(String name, MessageFactory messageFactory); + ExtendedLogger getLogger(String name, @Nullable 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(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, @Nullable 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(String key, 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(String key, 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(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(String key, 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..a075db94072 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -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(String fqcn, ClassLoader loader, boolean currentContext, 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(String fqcn, ClassLoader loader, boolean currentContext) { + return false; + } + /** * Creates a {@link LoggerContext}. * @@ -47,8 +78,13 @@ public interface LoggerContextFactory { * @param name The name of the context or null. * @return The LoggerContext. */ - LoggerContext getContext(String fqcn, ClassLoader loader, Object externalContext, boolean currentContext, - URI configLocation, String name); + LoggerContext getContext( + String fqcn, + ClassLoader loader, + Object externalContext, + boolean currentContext, + URI configLocation, + String name); /** * Removes knowledge of a LoggerContext. @@ -56,4 +92,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 index 113bc4546e8..cae2e1e95dd 100644 --- 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 @@ -1,47 +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.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(); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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..426629dc945 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownAware.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.spi; + +/** + * Interface allowing interested classes to know when a LoggerContext has shutdown - if the LoggerContext + * implementation provides a way to register listeners. + * + * @since 2.12.1 + */ +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..9c9fdc12d7f --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.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.spi; + +import java.util.List; + +/** + * LoggerContexts implementing this are able register LoggerContextShutdownAware classes. + * + * @since 2.12.1 + */ +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..4274337b145 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 @@ -1,44 +1,64 @@ /* * 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.logging.log4j.spi; -import java.util.ArrayList; +import static java.util.Objects.requireNonNull; + import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * Convenience class to be used by {@code LoggerContext} implementations. + * Convenience class to be used as an {@link ExtendedLogger} registry by {@code LoggerContext} implementations. + * @since 2.6 */ +@NullMarked public class LoggerRegistry { - private static final String DEFAULT_FACTORY_KEY = AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS.getName(); - private final MapFactory factory; - private final Map> map; + + private final Map> loggerByMessageFactoryByName = new HashMap<>(); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private final Lock readLock = lock.readLock(); + + private final Lock writeLock = lock.writeLock(); /** - * Interface to control the data structure used by the registry to store the Loggers. + * Data structure contract for the internal storage of admitted loggers. + * * @param subtype of {@code ExtendedLogger} + * @since 2.6 + * @deprecated As of version {@code 2.25.0}, planned to be removed! */ + @Deprecated public interface MapFactory { + Map createInnerMap(); Map> createOuterMap(); @@ -47,10 +67,15 @@ public interface MapFactory { } /** - * Generates ConcurrentHashMaps for use by the registry to store the Loggers. + * {@link MapFactory} implementation using {@link ConcurrentHashMap}. + * * @param subtype of {@code ExtendedLogger} + * @since 2.6 + * @deprecated As of version {@code 2.25.0}, planned to be removed! */ + @Deprecated public static class ConcurrentMapFactory implements MapFactory { + @Override public Map createInnerMap() { return new ConcurrentHashMap<>(); @@ -63,15 +88,20 @@ 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); } } /** - * Generates WeakHashMaps for use by the registry to store the Loggers. + * {@link MapFactory} implementation using {@link WeakHashMap}. + * * @param subtype of {@code ExtendedLogger} + * @since 2.6 + * @deprecated As of version {@code 2.25.0}, planned to be removed! */ + @Deprecated public static class WeakMapFactory implements MapFactory { + @Override public Map createInnerMap() { return new WeakHashMap<>(); @@ -88,95 +118,175 @@ public void putIfAbsent(final Map innerMap, final String name, final } } - public LoggerRegistry() { - this(new ConcurrentMapFactory()); - } - - public LoggerRegistry(final MapFactory factory) { - this.factory = Objects.requireNonNull(factory, "factory"); - this.map = factory.createOuterMap(); - } + public LoggerRegistry() {} - private static String factoryClassKey(final Class messageFactoryClass) { - return messageFactoryClass == null ? DEFAULT_FACTORY_KEY : messageFactoryClass.getName(); - } - - private static String factoryKey(final MessageFactory messageFactory) { - return messageFactory == null ? DEFAULT_FACTORY_KEY : messageFactory.getClass().getName(); + /** + * Constructs an instance ignoring the given the map factory. + * + * @param mapFactory a map factory + * @deprecated As of version {@code 2.25.0}, planned to be removed! + */ + @Deprecated + public LoggerRegistry(@Nullable final MapFactory mapFactory) { + this(); } /** - * Returns an ExtendedLogger. - * @param name The name of the Logger to return. - * @return The logger with the specified name. + * Returns the logger associated with the given name. + *

+ * There can be made no assumptions on the message factory of the returned logger. + * Callers are strongly advised to switch to {@link #getLogger(String, MessageFactory)} and provide a message factory parameter! + *

+ * + * @param name a logger name + * @return the logger associated with the name + * @deprecated As of version {@code 2.25.0}, planned to be removed! + * Use {@link #getLogger(String, MessageFactory)} instead. */ - public T getLogger(final String name) { - return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).get(name); + @Deprecated + public @Nullable T getLogger(final String name) { + requireNonNull(name, "name"); + return getLogger(name, null); } /** - * Returns 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. - * @return The logger with the specified name. + * Returns the logger associated with the given name and message factory. + *

+ * In the absence of a message factory, there can be made no assumptions on the message factory of the returned logger. + * This lenient behaviour is only kept for backward compatibility. + * Callers are strongly advised to provide a message factory parameter to the method! + *

+ * + * @param name a logger name + * @param messageFactory a message factory + * @return the logger associated with the given name and message factory */ - public T getLogger(final String name, final MessageFactory messageFactory) { - return getOrCreateInnerMap(factoryKey(messageFactory)).get(name); + public @Nullable T getLogger(final String name, @Nullable final MessageFactory messageFactory) { + requireNonNull(name, "name"); + readLock.lock(); + try { + final @Nullable Map loggerByMessageFactory = loggerByMessageFactoryByName.get(name); + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : ParameterizedMessageFactory.INSTANCE; + return loggerByMessageFactory == null ? null : loggerByMessageFactory.get(effectiveMessageFactory); + } finally { + readLock.unlock(); + } } public Collection getLoggers() { - return getLoggers(new ArrayList()); + readLock.lock(); + try { + return loggerByMessageFactoryByName.values().stream() + .flatMap(loggerByMessageFactory -> loggerByMessageFactory.values().stream()) + .collect(Collectors.toList()); + } finally { + readLock.unlock(); + } } public Collection getLoggers(final Collection destination) { - for (final Map inner : map.values()) { - destination.addAll(inner.values()); - } + requireNonNull(destination, "destination"); + destination.addAll(getLoggers()); return destination; } - private Map getOrCreateInnerMap(final String factoryName) { - Map inner = map.get(factoryName); - if (inner == null) { - inner = factory.createInnerMap(); - map.put(factoryName, inner); - } - return inner; - } - /** - * Detects if a Logger with the specified name exists. - * @param name The Logger name to search for. - * @return true if the Logger exists, false otherwise. + * Checks if a logger associated with the given name exists. + *

+ * There can be made no assumptions on the message factory of the found logger. + * Callers are strongly advised to switch to {@link #hasLogger(String, MessageFactory)} and provide a message factory parameter! + *

+ * + * @param name a logger name + * @return {@code true}, if the logger exists; {@code false} otherwise. + * @deprecated As of version {@code 2.25.0}, planned to be removed! + * Use {@link #hasLogger(String, MessageFactory)} instead. */ + @Deprecated public boolean hasLogger(final String name) { - return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).containsKey(name); + requireNonNull(name, "name"); + final @Nullable T logger = getLogger(name); + return logger != null; } /** - * Detects if a Logger with the specified name and MessageFactory exists. - * @param name The Logger name to search for. - * @param messageFactory The message factory to search for. - * @return true if the Logger exists, false otherwise. + * Checks if a logger associated with the given name and message factory exists. + *

+ * In the absence of a message factory, there can be made no assumptions on the message factory of the found logger. + * This lenient behaviour is only kept for backward compatibility. + * Callers are strongly advised to provide a message factory parameter to the method! + *

+ * + * @param name a logger name + * @param messageFactory a message factory + * @return {@code true}, if the logger exists; {@code false} otherwise. * @since 2.5 */ - public boolean hasLogger(final String name, final MessageFactory messageFactory) { - return getOrCreateInnerMap(factoryKey(messageFactory)).containsKey(name); + public boolean hasLogger(final String name, @Nullable final MessageFactory messageFactory) { + requireNonNull(name, "name"); + final @Nullable T logger = getLogger(name, messageFactory); + return logger != null; } /** - * Detects if a Logger with the specified name and MessageFactory type exists. - * @param name The Logger name to search for. - * @param messageFactoryClass The message factory class to search for. - * @return true if the Logger exists, false otherwise. + * Checks if a logger associated with the given name and message factory type exists. + * + * @param name a logger name + * @param messageFactoryClass a message factory class + * @return {@code true}, if the logger exists; {@code false} otherwise. * @since 2.5 */ public boolean hasLogger(final String name, final Class messageFactoryClass) { - return getOrCreateInnerMap(factoryClassKey(messageFactoryClass)).containsKey(name); + requireNonNull(name, "name"); + requireNonNull(messageFactoryClass, "messageFactoryClass"); + readLock.lock(); + try { + return loggerByMessageFactoryByName.getOrDefault(name, Collections.emptyMap()).keySet().stream() + .anyMatch(messageFactory -> messageFactoryClass.equals(messageFactory.getClass())); + } finally { + readLock.unlock(); + } + } + + /** + * Registers the provided logger. + *

+ * The logger will be registered using the keys provided by the {@code name} and {@code messageFactory} parameters + * and the values of {@link Logger#getName()} and {@link Logger#getMessageFactory()}. + *

+ * + * @param name a logger name + * @param messageFactory a message factory + * @param logger a logger instance + */ + public void putIfAbsent(final String name, @Nullable final MessageFactory messageFactory, final T logger) { + + // Check arguments + requireNonNull(name, "name"); + requireNonNull(logger, "logger"); + + // Insert the logger + writeLock.lock(); + try { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : ParameterizedMessageFactory.INSTANCE; + // Register using the keys provided by the caller + loggerByMessageFactoryByName + .computeIfAbsent(name, this::createLoggerRefByMessageFactoryMap) + .putIfAbsent(effectiveMessageFactory, logger); + // Also register using the values extracted from `logger` + if (!name.equals(logger.getName()) || !effectiveMessageFactory.equals(logger.getMessageFactory())) { + loggerByMessageFactoryByName + .computeIfAbsent(logger.getName(), this::createLoggerRefByMessageFactoryMap) + .putIfAbsent(logger.getMessageFactory(), logger); + } + } finally { + writeLock.unlock(); + } } - public void putIfAbsent(final String name, final MessageFactory messageFactory, final T logger) { - factory.putIfAbsent(getOrCreateInnerMap(factoryKey(messageFactory)), name, logger); + private Map createLoggerRefByMessageFactoryMap(final String ignored) { + return new WeakHashMap<>(); } } 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 index ff31515ef8f..1e0f4162c02 100644 --- 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 @@ -1,23 +1,22 @@ /* * 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.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; @@ -60,44 +59,84 @@ public Message newMessage(final String message, final Object p0, final Object p1 } @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, - final Object p3) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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); } 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..898982e4307 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -20,7 +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; @@ -35,6 +35,7 @@ public class MutableThreadContextStack implements ThreadContextStack, StringBuil * The underlying list (never null). */ private final List list; + private boolean frozen; /** @@ -46,7 +47,7 @@ public MutableThreadContextStack() { /** * Constructs a new instance. - * @param list + * @param list Initial elements to be stored in this stack implementation. */ public MutableThreadContextStack(final List list) { this.list = new ArrayList<>(list); @@ -211,10 +212,7 @@ public void formatTo(final StringBuilder buffer) { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.list == null) ? 0 : this.list.hashCode()); - return result; + return 31 + Objects.hashCode(list); } @Override @@ -230,14 +228,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(this.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..6de97d6235a 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 @@ -1,23 +1,25 @@ /* * 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.logging.log4j.spi; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * {@code ThreadContextMap} implementation used when either of system properties {@code disableThreadContextMap} or . @@ -25,10 +27,16 @@ * * @since 2.7 */ +@NullMarked public class NoOpThreadContextMap implements ThreadContextMap { + + /** + * @since 2.24.0 + */ + public static final ThreadContextMap INSTANCE = new NoOpThreadContextMap(); + @Override - public void clear() { - } + public void clear() {} @Override public boolean containsKey(final String key) { @@ -36,7 +44,7 @@ public boolean containsKey(final String key) { } @Override - public String get(final String key) { + public @Nullable String get(final String key) { return null; } @@ -46,7 +54,7 @@ public Map getCopy() { } @Override - public Map getImmutableMapOrNull() { + public @Nullable Map getImmutableMapOrNull() { return null; } @@ -56,10 +64,8 @@ public boolean isEmpty() { } @Override - public void put(final String key, final String value) { - } + public void put(final String key, final String value) {} @Override - public void remove(final String key) { - } + public void remove(final String key) {} } 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..5e312453fba 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -50,5 +50,4 @@ public interface ObjectThreadContextMap extends CleanableThreadContextMap { * @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..4b9ab3f8d51 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 @@ -1,64 +1,118 @@ /* * 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.logging.log4j.spi; import java.lang.ref.WeakReference; import java.net.URL; +import java.util.Objects; import java.util.Properties; - import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * Model class for a Log4j 2 provider. The properties in this class correspond to the properties used in a - * {@code META-INF/log4j-provider.properties} file. Note that this class is automatically created by Log4j and should - * not be used by providers. + * Service class used to bind the Log4j API with an implementation. + *

+ * Implementors should register an implementation of this class with {@link java.util.ServiceLoader}. + *

+ *

+ * Deprecated: the automatic registration of providers from + * {@code META-INF/log4j-provider.properties} is supported for compatibility reasons. Support for this file will + * be dropped in a future version. + *

*/ +@NullMarked public class Provider { + /** + * Constant inlined by the compiler + * @since 2.24.0 + */ + protected static final String CURRENT_VERSION = "2.6.0"; + /** * Property name to set for a Log4j 2 provider to specify the priority of this implementation. + * @since 2.0.1 + * @deprecated since 2.24.0 */ + @Deprecated public static final String FACTORY_PRIORITY = "FactoryPriority"; + /** - * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.ThreadContextMap}. + * Property name to set to the implementation of {@link ThreadContextMap}. + * @since 2.0.1 + * @deprecated since 2.24.0 */ + @Deprecated public static final String THREAD_CONTEXT_MAP = "ThreadContextMap"; + /** - * Property name to set to the implementation of {@link org.apache.logging.log4j.spi.LoggerContextFactory}. + * Property name to set to the implementation of {@link LoggerContextFactory}. + * @since 2.0.1 + * @deprecated since 2.24.0 */ + @Deprecated public static final String LOGGER_CONTEXT_FACTORY = "LoggerContextFactory"; - private static final Integer DEFAULT_PRIORITY = Integer.valueOf(-1); + /** + * System property used to specify the class name of the provider to use. + * @since 2.24.0 + */ + public static final String PROVIDER_PROPERTY_NAME = "log4j.provider"; + + private static final String DISABLE_CONTEXT_MAP = "log4j2.disableThreadContextMap"; + private static final String DISABLE_THREAD_CONTEXT = "log4j2.disableThreadContext"; + + private static final int DEFAULT_PRIORITY = -1; private static final Logger LOGGER = StatusLogger.getLogger(); - private final Integer priority; - private final String className; - private final Class loggerContextFactoryClass; - private final String threadContextMap; - private final Class threadContextMapClass; - private final String versions; - private final URL url; + private final int priority; + // LoggerContextFactory + @Deprecated + private final @Nullable String className; + + private final @Nullable Class loggerContextFactoryClass; + // ThreadContextMap + @Deprecated + private final @Nullable String threadContextMap; + + private final @Nullable Class threadContextMapClass; + private final @Nullable String versions; + + @Deprecated + private final @Nullable URL url; + + @Deprecated private final WeakReference classLoader; + /** + * Constructor used by the deprecated {@code META-INF/log4j-provider.properties} format. + * @since 2.0.1 + * @deprecated since 2.24.0 + */ + @Deprecated public Provider(final Properties props, final URL url, final ClassLoader classLoader) { this.url = url; this.classLoader = new WeakReference<>(classLoader); final String weight = props.getProperty(FACTORY_PRIORITY); - priority = weight == null ? DEFAULT_PRIORITY : Integer.valueOf(weight); + priority = weight == null ? DEFAULT_PRIORITY : Integer.parseInt(weight); className = props.getProperty(LOGGER_CONTEXT_FACTORY); threadContextMap = props.getProperty(THREAD_CONTEXT_MAP); loggerContextFactoryClass = null; @@ -66,36 +120,69 @@ public Provider(final Properties props, final URL url, final ClassLoader classLo versions = null; } - public Provider(final Integer priority, final String versions, - final Class loggerContextFactoryClass) { - this(priority, versions, loggerContextFactoryClass, null); + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}. + * @since 2.24.0 + */ + public Provider(final @Nullable Integer priority, final String versions) { + this(priority, versions, null, null); } + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}, + * @param loggerContextFactoryClass A public exported implementation of {@link LoggerContextFactory} or {@code + * null} if {@link #getLoggerContextFactory()} is also implemented. + * @since 2.9.0 + */ + public Provider( + final @Nullable Integer priority, + final String versions, + final @Nullable Class loggerContextFactoryClass) { + this(priority, versions, loggerContextFactoryClass, null); + } - public Provider(final Integer priority, final String versions, - final Class loggerContextFactoryClass, - final Class threadContextMapClass) { - this.url = null; - this.classLoader = null; - this.priority = priority; + /** + * @param priority A positive number specifying the provider's priority or {@code null} if default, + * @param versions Minimal API version required, should be set to {@link #CURRENT_VERSION}, + * @param loggerContextFactoryClass A public exported implementation of {@link LoggerContextFactory} or {@code + * null} if {@link #getLoggerContextFactory()} is also implemented, + * @param threadContextMapClass A public exported implementation of {@link ThreadContextMap} or {@code null} if + * {@link #getThreadContextMapInstance()} is implemented. + * @since 2.9.0 + */ + public Provider( + final @Nullable Integer priority, + final String versions, + final @Nullable Class loggerContextFactoryClass, + final @Nullable Class threadContextMapClass) { + this.priority = priority != null ? priority : DEFAULT_PRIORITY; + this.versions = versions; this.loggerContextFactoryClass = loggerContextFactoryClass; this.threadContextMapClass = threadContextMapClass; - this.className = null; - this.threadContextMap = null; - this.versions = versions; + // Deprecated + className = null; + threadContextMap = null; + url = null; + classLoader = new WeakReference<>(null); } /** * Returns the Log4j API versions supported by the implementation. * @return A String containing the Log4j versions supported. + * + * @since 2.9.0 */ public String getVersions() { - return versions; + return versions != null ? versions : ""; } /** * Gets the priority (natural ordering) of this Provider. - * + *

+ * Log4j selects the highest priority provider. + *

* @return the priority of this Provider */ public Integer getPriority() { @@ -103,151 +190,187 @@ public Integer getPriority() { } /** - * Gets the class name of the {@link org.apache.logging.log4j.spi.LoggerContextFactory} implementation of this - * Provider. + * Gets the class name of the {@link LoggerContextFactory} implementation of this Provider. * - * @return the class name of a LoggerContextFactory implementation + * @return the class name of a LoggerContextFactory implementation or {@code null} if unspecified. + * @see #loadLoggerContextFactory() */ - public String getClassName() { - if (loggerContextFactoryClass != null) { - return loggerContextFactoryClass.getName(); - } - return className; + public @Nullable String getClassName() { + return loggerContextFactoryClass != null ? loggerContextFactoryClass.getName() : className; } /** - * Loads the {@link org.apache.logging.log4j.spi.LoggerContextFactory} class specified by this Provider. + * Loads the {@link LoggerContextFactory} class specified by this Provider. * - * @return the LoggerContextFactory implementation class or {@code null} if there was an error loading it + * @return the LoggerContextFactory implementation class or {@code null} if unspecified or a loader error occurred. + * @since 2.0.1 */ - public Class loadLoggerContextFactory() { + public @Nullable Class loadLoggerContextFactory() { if (loggerContextFactoryClass != null) { return loggerContextFactoryClass; } - if (className == null) { - return null; - } + final String className = getClassName(); final ClassLoader loader = classLoader.get(); - if (loader == null) { + // Support for deprecated {@code META-INF/log4j-provider.properties} format. + // In the remaining cases {@code loader == null}. + if (loader == null || className == null) { return null; } try { final Class clazz = loader.loadClass(className); if (LoggerContextFactory.class.isAssignableFrom(clazz)) { return clazz.asSubclass(LoggerContextFactory.class); + } else { + LOGGER.error( + "Class {} specified in {} does not extend {}", + className, + getUrl(), + LoggerContextFactory.class.getName()); } } catch (final Exception e) { - LOGGER.error("Unable to create class {} specified in {}", className, url.toString(), e); + LOGGER.error("Unable to create class {} specified in {}", className, getUrl(), e); } return null; } + /** + * @return The logger context factory to be used by {@link org.apache.logging.log4j.LogManager}. + * @since 2.24.0 + */ + public LoggerContextFactory getLoggerContextFactory() { + final Class implementation = loadLoggerContextFactory(); + if (implementation != null) { + try { + return LoaderUtil.newInstanceOf(implementation); + } catch (final ReflectiveOperationException e) { + LOGGER.error("Failed to instantiate logger context factory {}.", implementation.getName(), e); + } + } + LOGGER.error("Falling back to simple logger context factory: {}", SimpleLoggerContextFactory.class.getName()); + return SimpleLoggerContextFactory.INSTANCE; + } + /** * Gets the class name of the {@link org.apache.logging.log4j.spi.ThreadContextMap} implementation of this Provider. * * @return the class name of a ThreadContextMap implementation */ - public String getThreadContextMap() { - if (threadContextMapClass != null) { - return threadContextMapClass.getName(); - } - return threadContextMap; + public @Nullable String getThreadContextMap() { + return threadContextMapClass != null ? threadContextMapClass.getName() : threadContextMap; } /** - * Loads the {@link org.apache.logging.log4j.spi.ThreadContextMap} class specified by this Provider. + * Loads the {@link ThreadContextMap} class specified by this Provider. * - * @return the ThreadContextMap implementation class or {@code null} if there was an error loading it + * @return the {@code ThreadContextMap} implementation class or {@code null} if unspecified or a loading error + * occurred. + * @since 2.0.1 */ - public Class loadThreadContextMap() { + public @Nullable Class loadThreadContextMap() { if (threadContextMapClass != null) { return threadContextMapClass; } - if (threadContextMap == null) { - return null; - } + final String threadContextMap = getThreadContextMap(); final ClassLoader loader = classLoader.get(); - if (loader == null) { + // Support for deprecated {@code META-INF/log4j-provider.properties} format. + // In the remaining cases {@code loader == null}. + if (loader == null || threadContextMap == null) { return null; } try { final Class clazz = loader.loadClass(threadContextMap); if (ThreadContextMap.class.isAssignableFrom(clazz)) { return clazz.asSubclass(ThreadContextMap.class); + } else { + LOGGER.error( + "Class {} specified in {} does not extend {}", + threadContextMap, + getUrl(), + ThreadContextMap.class.getName()); } } catch (final Exception e) { - LOGGER.error("Unable to create class {} specified in {}", threadContextMap, url.toString(), e); + LOGGER.error("Unable to load class {} specified in {}", threadContextMap, url, e); } return null; } + /** + * @return The thread context map to be used by {@link org.apache.logging.log4j.ThreadContext}. + * @since 2.24.0 + */ + public ThreadContextMap getThreadContextMapInstance() { + final Class implementation = loadThreadContextMap(); + if (implementation != null) { + try { + return LoaderUtil.newInstanceOf(implementation); + } catch (final ReflectiveOperationException e) { + LOGGER.error("Failed to instantiate logger context factory {}.", implementation.getName(), e); + } + } + final PropertiesUtil props = PropertiesUtil.getProperties(); + return props.getBooleanProperty(DISABLE_CONTEXT_MAP) || props.getBooleanProperty(DISABLE_THREAD_CONTEXT) + ? NoOpThreadContextMap.INSTANCE + : new DefaultThreadContextMap(); + } + /** * Gets the URL containing this Provider's Log4j details. * - * @return the URL corresponding to the Provider {@code META-INF/log4j-provider.properties} file + * @return the URL corresponding to the Provider {@code META-INF/log4j-provider.properties} file or {@code null} + * for a provider class. + * @deprecated since 2.24.0, without a replacement. */ - public URL getUrl() { + @Deprecated + public @Nullable URL getUrl() { return url; } - + @Override public String toString() { - final StringBuilder result = new StringBuilder("Provider["); - if (!DEFAULT_PRIORITY.equals(priority)) { - result.append("priority=").append(priority).append(", "); + final StringBuilder result = + new StringBuilder("Provider '").append(getClass().getName()).append("'"); + if (priority != DEFAULT_PRIORITY) { + result.append("\n\tpriority = ").append(priority); } + final String threadContextMap = getThreadContextMap(); if (threadContextMap != null) { - result.append("threadContextMap=").append(threadContextMap).append(", "); - } else if (threadContextMapClass != null) { - result.append("threadContextMapClass=").append(threadContextMapClass.getName()); + result.append("\n\tthreadContextMap = ").append(threadContextMap); } - if (className != null) { - result.append("className=").append(className).append(", "); - } else if (loggerContextFactoryClass != null) { - result.append("class=").append(loggerContextFactoryClass.getName()); + final String loggerContextFactory = getClassName(); + if (loggerContextFactory != null) { + result.append("\n\tloggerContextFactory = ").append(loggerContextFactory); } if (url != null) { - result.append("url=").append(url); + result.append("\n\turl = ").append(url); } - final ClassLoader loader; - if (classLoader == null || (loader = classLoader.get()) == null) { - result.append(", classLoader=null(not reachable)"); - } else { - result.append(", classLoader=").append(loader); + if (Provider.class.equals(getClass())) { + final ClassLoader loader = classLoader.get(); + if (loader == null) { + result.append("\n\tclassLoader = null or not reachable"); + } else { + result.append("\n\tclassLoader = ").append(loader); + } } - result.append("]"); return result.toString(); } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Provider provider = (Provider) o; - - if (priority != null ? !priority.equals(provider.priority) : provider.priority != null) { - return false; - } - if (className != null ? !className.equals(provider.className) : provider.className != null) { - return false; - } - if (loggerContextFactoryClass != null ? !loggerContextFactoryClass.equals(provider.loggerContextFactoryClass) : provider.loggerContextFactoryClass != null) { - return false; + if (o instanceof Provider) { + final Provider provider = (Provider) o; + return Objects.equals(priority, provider.priority) + && Objects.equals(className, provider.className) + && Objects.equals(loggerContextFactoryClass, provider.loggerContextFactoryClass) + && Objects.equals(versions, provider.versions); } - return versions != null ? versions.equals(provider.versions) : provider.versions == null; + return false; } @Override public int hashCode() { - int result = priority != null ? priority.hashCode() : 0; - result = 31 * result + (className != null ? className.hashCode() : 0); - result = 31 * result + (loggerContextFactoryClass != null ? loggerContextFactoryClass.hashCode() : 0); - result = 31 * result + (versions != null ? versions.hashCode() : 0); - return result; + return Objects.hash(priority, className, loggerContextFactoryClass, versions); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ReadOnlyThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ReadOnlyThreadContextMap.java index 7ffb78e446b..de9ae028aa2 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ReadOnlyThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ReadOnlyThreadContextMap.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.spi; import java.util.Map; - import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.util.StringMap; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/StandardLevel.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/StandardLevel.java index 8a10e2d7a61..c08d7177abc 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/StandardLevel.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/StandardLevel.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; @@ -73,7 +73,7 @@ public enum StandardLevel { /** * Returns the integer value of the Level. - * + * * @return the integer value of the Level. */ public int intLevel() { @@ -82,7 +82,7 @@ public int intLevel() { /** * Method to convert custom Levels into a StandardLevel for conversion to other systems. - * + * * @param intLevel The integer value of the Level. * @return The StandardLevel. */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Terminable.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Terminable.java index d828231ad31..fb93c54b63c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Terminable.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Terminable.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; 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..f0af90ddbb2 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.spi; import java.util.Map; - import org.apache.logging.log4j.ThreadContext; /** @@ -81,7 +80,7 @@ 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. */ 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..ccd7f86b672 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.spi; import java.util.Map; - import org.apache.logging.log4j.util.StringMap; /** 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 index c67fbcd699e..bf97c1aebba 100644 --- 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 @@ -1,27 +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. */ 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; /** @@ -46,87 +42,20 @@ * @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. + * + * @since 2.11.0 */ public static void init() { - CopyOnWriteSortedArrayThreadContextMap.init(); - GarbageFreeSortedArrayThreadContextMap.init(); - DefaultThreadContextMap.init(); - initPrivate(); + ProviderUtil.getProvider().getThreadContextMapInstance(); } - /** - * 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() { - } + 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); + return ProviderUtil.getProvider().getThreadContextMapInstance(); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextStack.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextStack.java index 04d2ece0bf8..5096aa1e021 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextStack.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextStack.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.spi; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java index 39549277c8f..3b6b5c25857 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java @@ -18,4 +18,9 @@ * Internal interfaces and classes to be used by authors of logging implementations or for internal use by * API classes. */ +@Export +@Version("2.25.0") package org.apache.logging.log4j.spi; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..ce23ff5aa9b 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 @@ -1,113 +1,179 @@ /* * 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.logging.log4j.status; +import static java.util.Objects.requireNonNull; + +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; - +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.apache.logging.log4j.Level; /** - * StatusListener that writes to the Console. + * A {@link StatusListener} that writes to the console. */ @SuppressWarnings("UseOfSystemOutOrSystemErr") public class StatusConsoleListener implements StatusListener { - private Level level = Level.FATAL; - private String[] filters; - private final PrintStream stream; + private final Lock lock = new ReentrantLock(); + + private final Level initialLevel; + + private final PrintStream initialStream; + + // `volatile` is necessary to correctly read the `level` without holding the lock + private volatile Level level; + + // `volatile` is necessary to correctly read the `stream` without holding the lock + private volatile PrintStream stream; /** - * 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.initialLevel = this.level = requireNonNull(level, "level"); + this.initialStream = this.stream = requireNonNull(stream, "stream"); } /** * Sets the level to a new value. - * @param level The new Level. + * + * @param level the new level + * @throws NullPointerException on null {@code level} */ public void setLevel(final Level level) { - this.level = level; + requireNonNull(level, "level"); + // Check if a mutation (and locking!) is necessary at all + if (!this.level.equals(level)) { + lock.lock(); + try { + this.level = level; + } finally { + lock.unlock(); + } + } } /** - * Return the Log Level for which the Listener should receive events. - * @return the Log Level. + * Sets the output stream to a new value. + * + * @param stream the new output stream + * @throws NullPointerException on null {@code stream} + * @since 2.23.0 + */ + public void setStream(final PrintStream stream) { + requireNonNull(stream, "stream"); + // Check if a mutation (and locking!) is necessary at all + if (this.stream != stream) { + @Nullable OutputStream oldStream = null; + lock.lock(); + try { + if (this.stream != stream) { + oldStream = this.stream; + this.stream = stream; + } + } finally { + lock.unlock(); + } + if (oldStream != null) { + closeNonSystemStream(oldStream); + } + } + } + + /** + * Returns the level for which the listener should receive events. + * + * @return the log level */ @Override public Level getStatusLevel() { - return this.level; + return level; } /** * Writes status messages to the console. - * @param data The StatusData. + * + * @param data a status data + * @throws NullPointerException on null {@code data} */ @Override public void log(final StatusData data) { - if (!filtered(data)) { - stream.println(data.getFormattedStatus()); - } + requireNonNull(data, "data"); + final String formattedStatus = data.getFormattedStatus(); + stream.println(formattedStatus); } /** * Adds package name filters to exclude. + * * @param filters An array of package names to exclude. + * @deprecated since 2.23.0, this method is ineffective and only kept for binary backward compatibility. */ - public void setFilters(final String... filters) { - this.filters = filters; - } + @Deprecated + public void setFilters(final String... filters) {} - private boolean filtered(final StatusData data) { - if (filters == null) { - return false; - } - final String caller = data.getStackTraceElement().getClassName(); - for (final String filter : filters) { - if (caller.startsWith(filter)) { - return true; - } + /** + * Resets the level and output stream to its initial values, and closes the output stream, if it is a non-system one. + */ + @Override + public void close() { + final OutputStream oldStream; + lock.lock(); + try { + oldStream = stream; + stream = initialStream; + level = initialLevel; + } finally { + lock.unlock(); } - return false; + closeNonSystemStream(oldStream); } - @Override - public void close() throws IOException { - // only want to close non-system streams - if (this.stream != System.out && this.stream != System.err) { - this.stream.close(); + private static void closeNonSystemStream(final OutputStream stream) { + // Close only non-system streams + if (stream != System.out && stream != System.err) { + try { + stream.close(); + } catch (IOException error) { + // We are at the lowest level of the system. + // Hence, there is nothing better we can do but dumping the failure. + error.printStackTrace(System.err); + } } } } 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..1b0dcf31dee 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 @@ -1,32 +1,34 @@ /* * 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.logging.log4j.status; +import static java.util.Objects.requireNonNull; +import static org.apache.logging.log4j.util.Chars.SPACE; + +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.Serializable; -import java.text.SimpleDateFormat; -import java.util.Date; - +import java.time.Instant; +import java.time.format.DateTimeFormatter; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.message.Message; -import static org.apache.logging.log4j.util.Chars.*; - /** * The Status data. */ @@ -34,54 +36,95 @@ public class StatusData implements Serializable { private static final long serialVersionUID = -4341916115118014017L; - private final long timestamp; + private final Instant instant; + + private final DateTimeFormatter instantFormatter; + + @Nullable private final StackTraceElement caller; + private final Level level; - private final Message msg; - private String threadName; + + private final Message message; + + private final String threadName; + + @Nullable private final Throwable throwable; /** - * Creates the StatusData object. - * - * @param caller The method that created the event. - * @param level The logging level. - * @param msg The message String. - * @param t The Error or Exception that occurred. - * @param threadName The thread name + * Constructs the instance using given properties. + * + * @param caller the method that created the event + * @param level a logging level + * @param message a message + * @param throwable the error occurred + * @param threadName the thread name + * @since 2.4 */ - public StatusData(final StackTraceElement caller, final Level level, final Message msg, final Throwable t, - final String threadName) { - this.timestamp = System.currentTimeMillis(); + public StatusData( + @Nullable final StackTraceElement caller, + final Level level, + final Message message, + @Nullable final Throwable throwable, + @Nullable final String threadName) { + this(caller, level, message, throwable, threadName, null, Instant.now()); + } + + StatusData( + @Nullable final StackTraceElement caller, + final Level level, + final Message message, + @Nullable final Throwable throwable, + @Nullable final String threadName, + @Nullable final DateTimeFormatter instantFormatter, + final Instant instant) { + // DateTimeFormatter.ISO_INSTANT is the default used in instant.toString() + this.instantFormatter = instantFormatter != null ? instantFormatter : DateTimeFormatter.ISO_INSTANT; + this.instant = instant; this.caller = caller; - this.level = level; - this.msg = msg; - this.throwable = t; - this.threadName = threadName; + this.level = requireNonNull(level, "level"); + this.message = requireNonNull(message, "message"); + this.throwable = throwable; + this.threadName = + threadName != null ? threadName : Thread.currentThread().getName(); + } + + /** + * Returns the instant of the event. + * + * @return the event's instant + * @since 2.23.0 + */ + public Instant getInstant() { + return instant; } /** - * Returns the event's timestamp. - * - * @return The event's timestamp. + * Returns the instant of the event. + * + * @return the event's instant + * @deprecated since 2.23.0, use {@link #getInstant()} instead. */ + @Deprecated public long getTimestamp() { - return timestamp; + return instant.toEpochMilli(); } /** - * Returns the StackTraceElement for the method that created the event. - * - * @return The StackTraceElement. + * Returns the method that created the event. + * + * @return the method that created the event */ + @Nullable public StackTraceElement getStackTraceElement() { return caller; } /** * Returns the logging level for the event. - * - * @return The logging level. + * + * @return the event's logging level */ public Level getLevel() { return level; @@ -89,57 +132,77 @@ public Level getLevel() { /** * Returns the message associated with the event. - * - * @return The message associated with the event. + * + * @return the message associated with the event */ public Message getMessage() { - return msg; + return message; } + /** + * Returns the name of the thread associated with the event. + * + * @return the name of the thread associated with the event + * @since 2.4 + */ public String getThreadName() { - if (threadName == null) { - threadName = Thread.currentThread().getName(); - } return threadName; } /** - * Returns the Throwable associated with the event. - * - * @return The Throwable associated with the event. + * Returns the error associated with the event. + * + * @return the error associated with the event */ + @Nullable public Throwable getThrowable() { return throwable; } /** - * Formats the StatusData for viewing. - * - * @return The formatted status data as a String. + * Formats the event in to a log line for viewing. + * + * @return the formatted event */ + @SuppressFBWarnings( + value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", + justification = "Log4j prints stacktraces only to logs, which should be private.") + @SuppressWarnings("DefaultCharset") public String getFormattedStatus() { final StringBuilder sb = new StringBuilder(); - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); - sb.append(format.format(new Date(timestamp))); + instantFormatter.formatTo(instant, sb); sb.append(SPACE); sb.append(getThreadName()); sb.append(SPACE); sb.append(level.toString()); sb.append(SPACE); - sb.append(msg.getFormattedMessage()); - final Object[] params = msg.getParameters(); - Throwable t; - if (throwable == null && params != null && params[params.length - 1] instanceof Throwable) { - t = (Throwable) params[params.length - 1]; + sb.append(message.getFormattedMessage()); + final Object[] parameters = message.getParameters(); + Throwable effectiveThrowable; + if (throwable == null + && parameters != null + && parameters.length > 0 + && parameters[parameters.length - 1] instanceof Throwable) { + effectiveThrowable = (Throwable) parameters[parameters.length - 1]; } else { - t = throwable; + effectiveThrowable = throwable; } - if (t != null) { - sb.append(SPACE); + if (effectiveThrowable != null) { + sb.append(System.lineSeparator()); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - t.printStackTrace(new PrintStream(baos)); - sb.append(baos.toString()); + effectiveThrowable.printStackTrace(new PrintStream(baos)); + /* + * https://errorprone.info/bugpattern/DefaultCharset + * + * Since Java 9 we'll be able to provide a charset. + */ + sb.append(baos); } return sb.toString(); } + + @Override + public String toString() { + return getMessage().getFormattedMessage(); + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusListener.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusListener.java index 4e407b33a03..c5288b61b68 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusListener.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusListener.java @@ -1,24 +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. */ package org.apache.logging.log4j.status; import java.io.Closeable; import java.util.EventListener; - import org.apache.logging.log4j.Level; /** 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..699dc66c8a7 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 @@ -1,285 +1,807 @@ /* * 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.logging.log4j.status; -import java.io.Closeable; +import static java.util.Objects.requireNonNull; + +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - 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; /** - * 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. - * - * @see SimpleLogger - * @see SimpleLoggerContext + * Records events that occur in the logging system. + * {@link StatusLogger} is expected to be a standalone, self-sufficient component that the logging system can rely on for low-level logging purposes. + *

Listeners

+ *

+ * Each recorded event will first get buffered and then used to notify the registered {@link StatusListener}s. + * If none are available, the fallback listener of type {@link StatusConsoleListener} will be used. + *

+ *

+ * You can programmatically register listeners using {@link #registerListener(StatusListener)} method. + *

+ *

Configuration

+ *

+ * The {@code StatusLogger} can be configured in following ways: + *

+ *
    + *
  1. Passing system properties to the Java process (e.g., {@code -Dlog4j2.StatusLogger.level=INFO})
  2. + *
  3. Providing properties in a {@value StatusLogger#PROPERTIES_FILE_NAME} file in the classpath
  4. + *
  5. Using Log4j configuration (i.e., {@code } in a {@code log4j2.xml} in the classpath)
  6. + *
  7. Programmatically (e.g., {@code StatusLogger.getLogger().setLevel(Level.WARN)})
  8. + *
+ *

+ * It is crucial to understand that there is a time between the first {@code StatusLogger} access and a configuration file (e.g., {@code log4j2.xml}) read. + * Consider the following example: + *

+ *
    + *
  1. The default level (of fallback listener) is {@code ERROR}
  2. + *
  3. You have {@code } in your {@code log4j2.xml}
  4. + *
  5. Until your {@code log4j2.xml} configuration is read, the effective level will be {@code ERROR}
  6. + *
  7. Once your {@code log4j2.xml} configuration is read, the effective level will be {@code WARN} as you configured
  8. + *
+ *

+ * Hence, unless you use either system properties or {@value StatusLogger#PROPERTIES_FILE_NAME} file in the classpath, there is a time window that only the defaults will be effective. + *

+ *

+ * {@code StatusLogger} is designed as a singleton class accessed statically. + * If you are running an application containing multiple Log4j configurations (e.g., in a servlet environment with multiple containers) and you happen to have differing {@code StatusLogger} configurations (e.g, one {@code log4j2.xml} containing {@code } while the other {@code }), the last loaded configuration will be the effective one. + *

+ *

Configuration properties

+ *

+ * The list of available properties for configuring the {@code StatusLogger} is shared below. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
available properties for configuring the StatusLogger
NameDefaultDescription
{@value MAX_STATUS_ENTRIES}0 + * The maximum number of events buffered. + * Once the limit is reached, older entries will be removed as new entries are added. + *
{@value DEFAULT_STATUS_LISTENER_LEVEL}{@code ERROR} + * The {@link Level} name to use as the fallback listener level.
+ * The fallback listener is used when the listener registry is empty. + * The fallback listener will accept entries filtered by the level provided in this configuration. + *
{@value STATUS_DATE_FORMAT}{@code null}A {@link java.time.format.DateTimeFormatter} pattern to format the created {@link StatusData}.
{@value #DEBUG_PROPERTY_NAME}falseThe debug mode toggle.
+ *

Debug mode

+ *

+ * When the {@value Constants#LOG4J2_DEBUG} system property is present, any level-related filtering will be skipped and all events will be notified to listeners. + * If no listeners are available, the fallback listener of type {@link StatusConsoleListener} will be used. + *

*/ -public final class StatusLogger extends AbstractLogger { +public class StatusLogger extends AbstractLogger { + + private static final long serialVersionUID = 2L; /** - * 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. + * The name of the system property that enables debug mode in its presence. + *

+ * This is a local clone of {@link Constants#LOG4J2_DEBUG}. + * The cloning is necessary to avoid cyclic initialization. + *

+ */ + private static final String DEBUG_PROPERTY_NAME = "log4j2.debug"; + + /** + * The name of the system property that can be configured with the maximum number of events buffered. + *

+ * 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. + * The default fallback listener buffer capacity. + *

+ * This constant is intended for tests. + *

+ * + * @see #MAX_STATUS_ENTRIES + */ + static final int DEFAULT_FALLBACK_LISTENER_BUFFER_CAPACITY = 0; + + /** + * The name of the system property that can be configured with the {@link Level} name to use as the fallback listener level. + *

+ * The fallback listener is used when the listener registry is empty. + * The fallback listener will accept entries filtered by the level provided in this configuration. + *

+ * + * @since 2.8 */ public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level"; - private static final long serialVersionUID = 2L; + /** + * The default fallback listener level. + *

+ * This constant is intended for tests and indeed makes things awfully confusing given the {@link #DEFAULT_STATUS_LISTENER_LEVEL} property, which is actually intended to be a property name, not its default value. + *

+ */ + static final Level DEFAULT_FALLBACK_LISTENER_LEVEL = Level.ERROR; - private static final String NOT_AVAIL = "?"; + /** + * The name of the system property that can be configured with a {@link java.time.format.DateTimeFormatter} pattern that will be used while formatting the created {@link StatusData}. + *

+ * When not provided, {@link Instant#toString()} will be used. + *

+ * + * @see #STATUS_DATE_FORMAT_ZONE + * @since 2.11.0 + */ + public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.dateFormat"; - private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); + /** + * The name of the system property that can be configured with a time-zone ID (e.g., {@code Europe/Amsterdam}, {@code UTC+01:00}) that will be used while formatting the created {@link StatusData}. + *

+ * When not provided, {@link ZoneId#systemDefault()} will be used. + *

+ * + * @see #STATUS_DATE_FORMAT + * @since 2.23.1 + */ + static final String STATUS_DATE_FORMAT_ZONE = "log4j2.StatusLogger.dateFormatZone"; - private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200); + /** + * The name of the file to be searched in the classpath to read properties from. + * + * @since 2.23.0 + */ + public static final String PROPERTIES_FILE_NAME = "log4j2.StatusLogger.properties"; - private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL); + /** + * Holder for user-provided {@link StatusLogger} configurations. + * + * @since 2.23.0 + */ + public static final class Config { + + private static final Config INSTANCE = new Config(); + + // Visible for tests + final boolean debugEnabled; + + // Visible for tests + final int bufferCapacity; + + // Visible for tests + @Nullable + final Level fallbackListenerLevel; + + // Visible for tests + @Nullable + final DateTimeFormatter instantFormatter; + + /** + * Constructs an instance using the given properties. + * Users should not create new instances, but use {@link #getInstance()} instead! + * + * @param debugEnabled the value of the {@value DEBUG_PROPERTY_NAME} property + * @param bufferCapacity the value of the {@value MAX_STATUS_ENTRIES} property + * @param instantFormatter the value of the {@value STATUS_DATE_FORMAT} property + */ + public Config(boolean debugEnabled, int bufferCapacity, @Nullable DateTimeFormatter instantFormatter) { + this.debugEnabled = debugEnabled; + if (bufferCapacity < 0) { + throw new IllegalArgumentException( + "was expecting a positive `bufferCapacity`, found: " + bufferCapacity); + } + this.bufferCapacity = bufferCapacity; + // Public ctor intentionally doesn't set `fallbackListenerLevel`. + // Because, if public ctor is used, it means user is programmatically creating a `Config` instance. + // Hence, they will use the public `StatusLogger` ctor too. + // There they need to provide the fallback listener explicitly anyway. + // Therefore, there is no need to ask for a `fallbackListenerLevel` here. + // Since this `fallbackListenerLevel` is only used by the private `StatusLogger` ctor. + this.fallbackListenerLevel = null; + this.instantFormatter = instantFormatter; + } - // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks. - private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(), - ParameterizedNoReferenceMessageFactory.INSTANCE); + /** + * Constructs the default instance using system properties and a property file (i.e., {@value Config#PROPERTIES_FILE_NAME}) in the classpath, if available. + */ + private Config() { + this(PropertiesUtilsDouble.readAllAvailableProperties()); + } - private final SimpleLogger logger; + /** + * A low-level constructor intended for tests. + */ + Config(final Properties... propertiesList) { + this(PropertiesUtilsDouble.normalizeProperties(propertiesList)); + } - private final Collection listeners = new CopyOnWriteArrayList<>(); + /** + * The lowest-level constructor. + */ + private Config(final Map normalizedProperties) { + this.debugEnabled = readDebugEnabled(normalizedProperties); + this.bufferCapacity = readBufferCapacity(normalizedProperties); + this.fallbackListenerLevel = readFallbackListenerLevel(normalizedProperties); + this.instantFormatter = readInstantFormatter(normalizedProperties); + } - @SuppressWarnings("NonSerializableFieldInSerializableClass") - // ReentrantReadWriteLock is Serializable - private final ReadWriteLock listenersLock = new ReentrantReadWriteLock(); + /** + * Gets the static instance. + * + * @return a singleton instance + */ + public static Config getInstance() { + return INSTANCE; + } - private final Queue messages = new BoundedQueue<>(MAX_ENTRIES); + private static boolean readDebugEnabled(final Map normalizedProperties) { + final String debug = PropertiesUtilsDouble.readProperty(normalizedProperties, DEBUG_PROPERTY_NAME); + return debug != null && !"false".equalsIgnoreCase(debug); + } - @SuppressWarnings("NonSerializableFieldInSerializableClass") - // ReentrantLock is Serializable - private final Lock msgLock = new ReentrantLock(); + private static int readBufferCapacity(final Map normalizedProperties) { + final String propertyName = MAX_STATUS_ENTRIES; + final String capacityString = PropertiesUtilsDouble.readProperty(normalizedProperties, propertyName); + final int defaultCapacity = DEFAULT_FALLBACK_LISTENER_BUFFER_CAPACITY; + int effectiveCapacity = defaultCapacity; + if (capacityString != null) { + try { + final int capacity = Integer.parseInt(capacityString); + if (capacity < 0) { + final String message = + String.format("was expecting a positive buffer capacity, found: %d", capacity); + throw new IllegalArgumentException(message); + } + effectiveCapacity = capacity; + } catch (final Exception error) { + final String message = String.format( + "Failed reading the buffer capacity from the `%s` property: `%s`. Falling back to the default: %d.", + propertyName, capacityString, defaultCapacity); + final IllegalArgumentException extendedError = new IllegalArgumentException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); + } + } + return effectiveCapacity; + } - private int listenersLevel; + private static Level readFallbackListenerLevel(final Map normalizedProperties) { + final String propertyName = DEFAULT_STATUS_LISTENER_LEVEL; + final String level = PropertiesUtilsDouble.readProperty(normalizedProperties, propertyName); + final Level defaultLevel = DEFAULT_FALLBACK_LISTENER_LEVEL; + try { + return level != null ? Level.valueOf(level) : defaultLevel; + } catch (final Exception error) { + final String message = String.format( + "Failed reading the level from the `%s` property: `%s`. Falling back to the default: `%s`.", + propertyName, level, defaultLevel); + final IllegalArgumentException extendedError = new IllegalArgumentException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); + return defaultLevel; + } + } - 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(); + @Nullable + private static DateTimeFormatter readInstantFormatter(final Map normalizedProperties) { + + // Read the format + final String formatPropertyName = STATUS_DATE_FORMAT; + final String format = PropertiesUtilsDouble.readProperty(normalizedProperties, formatPropertyName); + if (format == null) { + return null; + } + final DateTimeFormatter formatter; + try { + formatter = DateTimeFormatter.ofPattern(format); + } catch (final Exception error) { + final String message = String.format( + "failed reading the instant format from the `%s` property: `%s`", formatPropertyName, format); + final IllegalArgumentException extendedError = new IllegalArgumentException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); + return null; + } - // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging - if (isDebugPropertyEnabled()) { - logger.setLevel(Level.TRACE); + // Read the zone + final String zonePropertyName = STATUS_DATE_FORMAT_ZONE; + final String zoneIdString = PropertiesUtilsDouble.readProperty(normalizedProperties, zonePropertyName); + final ZoneId defaultZoneId = ZoneId.systemDefault(); + ZoneId zoneId = defaultZoneId; + if (zoneIdString != null) { + try { + zoneId = ZoneId.of(zoneIdString); + } catch (final Exception error) { + final String message = String.format( + "Failed reading the instant formatting zone ID from the `%s` property: `%s`. Falling back to the default: `%s`.", + zonePropertyName, zoneIdString, defaultZoneId); + final IllegalArgumentException extendedError = new IllegalArgumentException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); + } + } + return formatter.withZone(zoneId); } } - // 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); + /** + * This is a thin double of {@link org.apache.logging.log4j.util.PropertiesUtil}. + *

+ * We could have used {@code PropertiesUtil}, {@link org.apache.logging.log4j.util.PropertyFilePropertySource}, etc. + * Consequently, they would delegate to {@link org.apache.logging.log4j.util.LoaderUtil}, etc. + * All these mechanisms expect a working {@code StatusLogger}. + * In order to be self-sufficient, we cannot rely on them, hence this double. + *

+ */ + static final class PropertiesUtilsDouble { + + @Nullable + static String readProperty(final Map normalizedProperties, final String propertyName) { + final String normalizedPropertyName = normalizePropertyName(propertyName); + final Object value = normalizedProperties.get(normalizedPropertyName); + return (value instanceof String) ? (String) value : null; + } + + static Map readAllAvailableProperties() { + final Properties systemProperties = System.getProperties(); + final Properties environmentProperties = readEnvironmentProperties(); + final Properties fileProvidedProperties = readPropertiesFile(PROPERTIES_FILE_NAME); + return normalizeProperties(systemProperties, environmentProperties, fileProvidedProperties); + } + + private static Properties readEnvironmentProperties() { + final Properties properties = new Properties(); + properties.putAll(System.getenv()); + return properties; + } + + // We need to roll out our own `.properties` reader. + // We could have used `PropertiesUtil`, `PropertyFilePropertySource`, etc. + // Consequently, they would delegate to `LoaderUtil`, etc. + // All these mechanisms expect a working `StatusLogger`. + // Hence, in order to be self-sufficient, we cannot rely on them. + static Properties readPropertiesFile(final String propertiesFileName) { + final Properties properties = new Properties(); + // Unlike `ClassLoader#getResource()`, which takes absolute resource paths, `Class#getResource()` supports + // relative resource paths. Without a `/` prefix, the resource must be placed into JAR resources as + // `org/apache/logging/log4j/status/log4j2.StatusLogger.properties`. Hence, the `/` prefix. + final String resourceName = '/' + propertiesFileName; + final URL url = StatusLogger.class.getResource(resourceName); + if (url == null) { + return properties; + } + try (final InputStream stream = url.openStream()) { + properties.load(stream); + } catch (final IOException error) { + final String message = String.format("failed reading properties from `%s`", propertiesFileName); + final RuntimeException extendedError = new RuntimeException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); + } + return properties; + } + + private static Map normalizeProperties(Properties... propertiesList) { + Map map = new HashMap<>(); + for (Properties properties : propertiesList) { + properties.forEach((name, value) -> { + final boolean relevant = isRelevantPropertyName(name); + if (relevant) { + final String normalizedName = normalizePropertyName((String) name); + map.put(normalizedName, value); + } + }); + } + return map; + } + + /** + * Filter to exclude irrelevant property names (i.e., non-string and not {@code log4j}-prefixed) to speed up matching. + * @param propertyName a property name + * @return {@code true}, if the property name is relevant; {@code false}, otherwise + */ + private static boolean isRelevantPropertyName(@Nullable final Object propertyName) { + return propertyName instanceof String && ((String) propertyName).matches("^(?i)log4j.*"); + } + + /** + * An imperfect property name normalization routine. + *

+ * It is imperfect, because {@code foo.bar} would match with {@code fo.obar}. + * But it is good enough for the {@code StatusLogger} needs. + *

+ * + * @param propertyName the input property name + * @return the normalized property name + */ + private static String normalizePropertyName(final String propertyName) { + return propertyName + // Remove separators: + // - dots (properties) + // - dashes (kebab-case) + // - underscores (environment variables) + .replaceAll("[._-]", "") + // Replace all non-ASCII characters. + // Don't remove, otherwise `fooàö` would incorrectly match with `foo`. + // It is safe to replace them with dots, since we've just removed all dots above. + .replaceAll("\\P{InBasic_Latin}", ".") + // Lowercase ASCII – this is safe, since we've just removed all non-ASCII + .toLowerCase(Locale.US) + .replaceAll("^log4j2", "log4j"); + } + } + + /** + * Wrapper for the default instance for lazy initialization. + *

+ * The initialization will be performed when the JVM initializes the class. + * Since {@code InstanceHolder} has no other fields or methods, class initialization occurs when the {@code INSTANCE} field is first referenced. + *

+ * + * @see Double-checked locking: Clever, but broken + */ + private static final class InstanceHolder { + + private static volatile StatusLogger INSTANCE = new StatusLogger(); + } + + private final Config config; + + private final StatusConsoleListener fallbackListener; + + private final List listeners; + + private final transient ReadWriteLock listenerLock = new ReentrantReadWriteLock(); + + private final transient Lock listenerReadLock = listenerLock.readLock(); + + private final transient Lock listenerWriteLock = listenerLock.writeLock(); + + private final Queue buffer = new ConcurrentLinkedQueue<>(); + + /** + * Constructs the default instance. + *

+ * This method is visible for tests. + *

+ */ + StatusLogger() { + this( + StatusLogger.class.getSimpleName(), + ParameterizedNoReferenceMessageFactory.INSTANCE, + Config.getInstance(), + new StatusConsoleListener(requireNonNull(Config.getInstance().fallbackListenerLevel), System.err)); + } + + /** + * Constructs an instance using given properties. + * Users should not create new instances, but use {@link #getLogger()} instead! + * + * @param name the logger name + * @param messageFactory the message factory + * @param config the configuration + * @param fallbackListener the fallback listener + * @throws NullPointerException on null {@code name}, {@code messageFactory}, {@code config}, or {@code fallbackListener} + * @since 2.23.0 + */ + public StatusLogger( + final String name, + final MessageFactory messageFactory, + final Config config, + final StatusConsoleListener fallbackListener) { + super(requireNonNull(name, "name"), requireNonNull(messageFactory, "messageFactory")); + this.config = requireNonNull(config, "config"); + this.fallbackListener = requireNonNull(fallbackListener, "fallbackListener"); + this.listeners = new ArrayList<>(); } /** - * Retrieve the StatusLogger. + * Gets the static instance. * - * @return The StatusLogger. + * @return the singleton instance */ public static StatusLogger getLogger() { - return STATUS_LOGGER; + return InstanceHolder.INSTANCE; + } + + /** + * Sets the static (i.e., singleton) instance returned by {@link #getLogger()}. + * This method is intended for testing purposes and can have unforeseen consequences if used in production code. + * + * @param logger a logger instance + * @throws NullPointerException on null {@code logger} + * @since 2.23.0 + */ + public static void setLogger(final StatusLogger logger) { + InstanceHolder.INSTANCE = requireNonNull(logger, "logger"); + } + + /** + * Returns the fallback listener. + * + * @return the fallback listener + * @since 2.23.0 + */ + public StatusConsoleListener getFallbackListener() { + return fallbackListener; } + /** + * Sets the level of the fallback listener. + * + * @param level a level + * @deprecated Since 2.23.0, instead use the {@link StatusConsoleListener#setLevel(Level) setLevel(Level)} method + * on the fallback listener returned by {@link #getFallbackListener()}. + */ + @Deprecated public void setLevel(final Level level) { - logger.setLevel(level); + requireNonNull(level, "level"); + fallbackListener.setLevel(level); } /** * Registers a new listener. * - * @param listener The StatusListener to register. + * @param listener a listener to register */ public void registerListener(final StatusListener listener) { - listenersLock.writeLock().lock(); + requireNonNull(listener, "listener"); + listenerWriteLock.lock(); try { listeners.add(listener); - final Level lvl = listener.getStatusLevel(); - if (listenersLevel < lvl.intLevel()) { - listenersLevel = lvl.intLevel(); - } } finally { - listenersLock.writeLock().unlock(); + listenerWriteLock.unlock(); } } /** - * Removes a StatusListener. + * Removes the given listener. * - * @param listener The StatusListener to remove. + * @param listener a listener to remove */ public void removeListener(final StatusListener listener) { - closeSilently(listener); - listenersLock.writeLock().lock(); + requireNonNull(listener, "listener"); + listenerWriteLock.lock(); try { listeners.remove(listener); - int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); - for (final StatusListener statusListener : listeners) { - final int level = statusListener.getStatusLevel().intLevel(); - if (lowest < level) { - lowest = level; - } - } - listenersLevel = lowest; + closeListenerSafely(listener); } finally { - listenersLock.writeLock().unlock(); + listenerWriteLock.unlock(); } } - public void updateListenerLevel(final Level status) { - if (status.intLevel() > listenersLevel) { - listenersLevel = status.intLevel(); - } + /** + * Sets the level of the fallback listener. + * + * @param level a level + * @since 2.6 + * @deprecated Since 2.23.0, instead use the {@link StatusConsoleListener#setLevel(Level) setLevel(Level)} method + * on the fallback listener returned by {@link #getFallbackListener()}. + */ + @Deprecated + public void updateListenerLevel(final Level level) { + requireNonNull(level, "level"); + fallbackListener.setLevel(level); } /** - * Returns a thread safe Iterable for the StatusListener. + * Returns the listener collection. * - * @return An Iterable for the list of StatusListeners. + * @return a thread-safe read-only collection of listeners */ public Iterable getListeners() { - return listeners; + listenerReadLock.lock(); + try { + return Collections.unmodifiableCollection(listeners); + } finally { + listenerReadLock.unlock(); + } } /** - * Clears the list of status events and listeners. + * Clears the event buffer, removes the registered (not the fallback one!) listeners, and resets the fallback listener. */ public void reset() { - listenersLock.writeLock().lock(); + listenerWriteLock.lock(); try { - for (final StatusListener listener : listeners) { - closeSilently(listener); + final Iterator listenerIterator = listeners.iterator(); + while (listenerIterator.hasNext()) { + final StatusListener listener = listenerIterator.next(); + closeListenerSafely(listener); + listenerIterator.remove(); } } finally { - listeners.clear(); - listenersLock.writeLock().unlock(); - // note this should certainly come after the unlock to avoid unnecessary nested locking - clear(); + listenerWriteLock.unlock(); } + fallbackListener.close(); + buffer.clear(); } - private static void closeSilently(final Closeable resource) { + private static void closeListenerSafely(final StatusListener listener) { try { - resource.close(); - } catch (final IOException ignored) { - // ignored + listener.close(); + } catch (final IOException error) { + final String message = String.format("failed closing listener: %s", listener); + final RuntimeException extendedError = new RuntimeException(message, error); + // There is no logging system at this stage. + // There is nothing we can do but simply dumping the failure. + extendedError.printStackTrace(System.err); } } /** - * Returns a List of all events as StatusData objects. + * Returns buffered events. * - * @return The list of StatusData objects. + * @deprecated Since 2.23.0, instead of relying on the buffering provided by {@code StatusLogger}, + * users should register their own listeners to access to logged events. + * @return a thread-safe read-only collection of buffered events */ + @Deprecated public List getStatusData() { - msgLock.lock(); - try { - return new ArrayList<>(messages); - } finally { - msgLock.unlock(); - } + // Wrapping the buffer clone with an unmodifiable list. + // By disallowing modifications, we make it clear to the user that mutations will not get propagated. + // `Collections.unmodifiableList(new ArrayList<>(...))` should be replaced with `List.of()` in Java 9+. + return Collections.unmodifiableList(new ArrayList<>(buffer)); } /** - * Clears the list of status events. + * Clears the event buffer. + * + * @deprecated Since 2.23.0, instead of relying on the buffering provided by {@code StatusLogger}, + * users should register their own listeners to access to logged events. */ + @Deprecated public void clear() { - msgLock.lock(); - try { - messages.clear(); - } finally { - msgLock.unlock(); - } + buffer.clear(); } + /** + * Returns the least specific level among listeners, if registered any; otherwise, the fallback listener level. + * + * @return the least specific listener level, if registered any; otherwise, the fallback listener level + */ @Override public Level getLevel() { - return logger.getLevel(); + Level leastSpecificLevel = fallbackListener.getStatusLevel(); + // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation) + for (int listenerIndex = 0; listenerIndex < listeners.size(); listenerIndex++) { + final StatusListener listener = listeners.get(listenerIndex); + final Level listenerLevel = listener.getStatusLevel(); + if (listenerLevel.isLessSpecificThan(leastSpecificLevel)) { + leastSpecificLevel = listenerLevel; + } + } + return leastSpecificLevel; } - /** - * Adds an event. - * - * @param marker The Marker - * @param fqcn The fully qualified class name of the caller - * @param level The logging level - * @param msg The message associated with the event. - * @param t A Throwable or null. - */ @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, - final Throwable t) { - StackTraceElement element = null; - if (fqcn != null) { - element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace()); + @SuppressFBWarnings("INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE") + public void logMessage( + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable throwable) { + try { + final StatusData statusData = createStatusData(fqcn, level, message, throwable); + buffer(statusData); + notifyListeners(statusData); + } catch (final Exception error) { + // We are at the lowest level of the system. + // Hence, there is nothing better we can do but dumping the failure. + error.printStackTrace(System.err); } - final StatusData data = new StatusData(element, level, msg, t, null); - msgLock.lock(); + } + + private void buffer(final StatusData statusData) { + if (config.bufferCapacity == 0) { + return; + } + buffer.add(statusData); + while (buffer.size() >= config.bufferCapacity) { + buffer.remove(); + } + } + + private void notifyListeners(final StatusData statusData) { + final boolean foundListeners; + listenerReadLock.lock(); try { - messages.add(data); + foundListeners = !listeners.isEmpty(); + listeners.forEach(listener -> notifyListener(listener, statusData)); } finally { - msgLock.unlock(); + listenerReadLock.unlock(); } - // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled - if (isDebugPropertyEnabled()) { - 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); - } - } - } else { - logger.logMessage(fqcn, level, marker, msg, t); - } + if (!foundListeners) { + notifyListener(fallbackListener, statusData); } } - private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) { + private void notifyListener(final StatusListener listener, final StatusData statusData) { + final boolean levelEnabled = isLevelEnabled(listener.getStatusLevel(), statusData.getLevel()); + if (levelEnabled) { + listener.log(statusData); + } + } + + private StatusData createStatusData( + @Nullable final String fqcn, + final Level level, + final Message message, + @Nullable final Throwable throwable) { + final StackTraceElement caller = getStackTraceElement(fqcn); + final Instant instant = Instant.now(); + return new StatusData(caller, level, message, throwable, null, config.instantFormatter, instant); + } + + @Nullable + private static StackTraceElement getStackTraceElement(@Nullable final String fqcn) { if (fqcn == null) { return null; } boolean next = false; + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (final StackTraceElement element : stackTrace) { final String className = element.getClassName(); if (next && !fqcn.equals(className)) { @@ -287,7 +809,7 @@ private StackTraceElement getStackTraceElement(final String fqcn, final StackTra } if (fqcn.equals(className)) { next = true; - } else if (NOT_AVAIL.equals(className)) { + } else if ("?".equals(className)) { break; } } @@ -295,7 +817,7 @@ private StackTraceElement getStackTraceElement(final String fqcn, final StackTra } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { + public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable throwable) { return isEnabled(level, marker); } @@ -315,117 +837,158 @@ public boolean isEnabled(final Level level, final Marker marker, final String me } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1) { + public boolean isEnabled( + final Level level, final Marker marker, final String message, final Object p0, final Object p1) { return isEnabled(level, marker); } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { + 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); } @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) { + 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); } @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, + 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); } @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) { + 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); } @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) { + 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); } @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, + 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); } @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) { + 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); } @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) { + 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); } @Override - public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { + public boolean isEnabled( + final Level level, final Marker marker, final CharSequence message, final Throwable throwable) { return isEnabled(level, marker); } @Override - public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { + public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable throwable) { return isEnabled(level, marker); } @Override - public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { + public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable throwable) { return isEnabled(level, marker); } @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()) { - return true; - } - if (listeners.size() > 0) { - return listenersLevel >= level.intLevel(); - } - return logger.isEnabled(level, marker); + public boolean isEnabled(final Level messageLevel, final Marker marker) { + requireNonNull(messageLevel, "messageLevel"); + final Level loggerLevel = getLevel(); + return isLevelEnabled(loggerLevel, messageLevel); } /** - * Queues for status events. + * Checks if the message level is allowed for the filtering level (e.g., of logger, of listener) by taking debug mode into account. * - * @param Object type to be stored in the queue. + * @param filteringLevel the level (e.g., of logger, of listener) to filter messages + * @param messageLevel the level of the message + * @return {@code true}, if the sink level is less specific than the message level; {@code false}, otherwise */ - private class BoundedQueue extends ConcurrentLinkedQueue { - - private static final long serialVersionUID = -3945953719763255337L; - - private final int size; - - BoundedQueue(final int size) { - this.size = size; - } - - @Override - public boolean add(final E object) { - super.add(object); - while (messages.size() > size) { - messages.poll(); - } - return size > 0; - } + private boolean isLevelEnabled(final Level filteringLevel, final Level messageLevel) { + return config.debugEnabled || filteringLevel.isLessSpecificThan(messageLevel); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java index b5be7696fbb..12fddb60395 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/package-info.java @@ -18,4 +18,9 @@ /** Status API for Log4j 2. Should not be used by typical applications performing logging but may be * used by applications reporting on the status of the logging system */ +@Export +@Version("2.23.1") package org.apache.logging.log4j.status; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..44766a18d3c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -20,17 +20,18 @@ import java.security.Permission; import java.util.Collection; import java.util.List; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.status.StatusLogger; +import org.osgi.annotation.bundle.Header; import org.osgi.framework.AdaptPermission; import org.osgi.framework.AdminPermission; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; +import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.SynchronousBundleListener; @@ -43,7 +44,11 @@ * {@link org.apache.logging.log4j.spi.LoggerContextFactory} et al. that have corresponding * {@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. + * @since 2.0.1 */ +@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}") +@Header(name = Constants.BUNDLE_ACTIVATIONPOLICY, value = Constants.ACTIVATION_LAZY) +@InternalApi public class Activator implements BundleActivator, SynchronousBundleListener { private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); @@ -69,7 +74,10 @@ private void loadProvider(final Bundle bundle) { checkPermission(new AdaptPermission(BundleWiring.class.getName(), bundle, AdaptPermission.ADAPT)); final BundleContext bundleContext = bundle.getBundleContext(); if (bundleContext == null) { - LOGGER.debug("Bundle {} has no context (state={}), skipping loading provider", bundle.getSymbolicName(), toStateString(bundle.getState())); + LOGGER.debug( + "Bundle {} has no context (state={}), skipping loading provider", + bundle.getSymbolicName(), + toStateString(bundle.getState())); } else { loadProvider(bundleContext, bundle.adapt(BundleWiring.class)); } @@ -82,27 +90,28 @@ private void loadProvider(final Bundle bundle) { private String toStateString(final int state) { switch (state) { - case Bundle.UNINSTALLED: - return "UNINSTALLED"; - case Bundle.INSTALLED: - return "INSTALLED"; - case Bundle.RESOLVED: - return "RESOLVED"; - case Bundle.STARTING: - return "STARTING"; - case Bundle.STOPPING: - return "STOPPING"; - case Bundle.ACTIVE: - return "ACTIVE"; - default: - return Integer.toString(state); + case Bundle.UNINSTALLED: + return "UNINSTALLED"; + case Bundle.INSTALLED: + return "INSTALLED"; + case Bundle.RESOLVED: + return "RESOLVED"; + case Bundle.STARTING: + return "STARTING"; + case Bundle.STOPPING: + return "STOPPING"; + case Bundle.ACTIVE: + return "ACTIVE"; + default: + return Integer.toString(state); } } private void loadProvider(final BundleContext bundleContext, final BundleWiring bundleWiring) { - final String filter = "(APIVersion>=2.60)"; + final String filter = "(APIVersion>=2.6.0)"; try { - final Collection> serviceReferences = bundleContext.getServiceReferences(Provider.class, filter); + final Collection> serviceReferences = + bundleContext.getServiceReferences(Provider.class, filter); Provider maxProvider = null; for (final ServiceReference serviceReference : serviceReferences) { final Provider provider = bundleContext.getService(serviceReference); @@ -164,5 +173,4 @@ public void bundleChanged(final BundleEvent event) { break; } } - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.java new file mode 100644 index 00000000000..b2c3f5fcafe --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Base64Util.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.util; + +import java.nio.charset.Charset; +import java.util.Base64; + +/** + * Base64 encodes Strings. This utility is only necessary because the mechanism to do this changed in Java 8 and + * the original method was removed in Java 9. + * + * @since 2.12.0 + */ +public final class Base64Util { + + private static final Base64.Encoder ENCODER = Base64.getEncoder(); + + private Base64Util() {} + + /** + * This method does not specify an encoding for the {@code str} parameter and should not be used. + * @deprecated since 2.22.0, use {@link java.util.Base64} instead. + */ + @Deprecated + public static String encode(final String str) { + return str != null ? ENCODER.encodeToString(str.getBytes(Charset.defaultCharset())) : null; + } +} 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..e7cedb6bb3b 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -24,12 +24,14 @@ * @see ReadOnlyStringMap * @since 2.7 */ -public interface BiConsumer { +@FunctionalInterface +public interface BiConsumer extends java.util.function.BiConsumer { /** * Performs the operation given the specified arguments. * @param k the first input argument * @param v the second input argument */ + @Override void accept(K k, V v); } 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..46f6954cc0d --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.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.util; + +/** + * @since 2.22.0 + */ +@InternalApi +public final class Cast { + + /** + * Returns the provided object cast to the generic parameter type or null when the argument is null. + * + * @param o object to cast + * @param the type to cast + * @return object after casting or null if the object was null + * @throws ClassCastException if the object cannot be cast to the provided type + */ + public static T cast(final Object o) { + if (o == null) { + return null; + } + @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..509f15811f2 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 @@ -1,24 +1,26 @@ /* * 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.logging.log4j.util; /** * Consider this class private. + * @since 2.3 */ +@InternalApi public final class Chars { /** Carriage Return. */ @@ -33,6 +35,12 @@ public final class Chars { /** Line Feed. */ public static final char LF = '\n'; + /** + * NUL. + * @since 2.11.0 + */ + public static final char NUL = 0; + /** Single Quote [']. */ public static final char QUOTE = '\''; @@ -47,6 +55,7 @@ public final class Chars { * * @param digit a number 0 - 15 * @return the hex character for that digit or '\0' if invalid + * @since 2.8.2 */ public static char getUpperCaseHex(final int digit) { if (digit < 0 || digit >= 16) { @@ -60,6 +69,7 @@ public static char getUpperCaseHex(final int digit) { * * @param digit a number 0 - 15 * @return the hex character for that digit or '\0' if invalid + * @since 2.8.2 */ public static char getLowerCaseHex(final int digit) { if (digit < 0 || digit >= 16) { @@ -80,6 +90,5 @@ private static char getLowerCaseAlphaDigit(final int digit) { return (char) ('a' + digit - 10); } - private Chars() { - } + private Chars() {} } 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..7d665b9bff8 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 @@ -1,21 +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. */ package org.apache.logging.log4j.util; +import org.apache.logging.log4j.spi.Provider; + /** * Log4j API Constants. * @@ -23,44 +25,74 @@ */ public final class Constants { /** - * {@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. + * Specifies whether Log4j is used in a servlet container + *

+ * If {@code true} Log4j disables the features, which are incompatible with a typical servlet application: + *

+ *
    + *
  1. It disables the usage of {@link ThreadLocal}s for object pooling (unless the user has explicitly provided a {@link #ENABLE_THREADLOCALS} property)
  2. + *
  3. It uses a web-application safe implementation of {@link org.apache.logging.log4j.spi.ThreadContextMap} + * (see {@link Provider#getThreadContextMap()}),
  4. + *
  5. It disables the shutdown hook,
  6. + *
  7. It uses the caller thread to send JMX notifications.
  8. + *
+ *

+ * The value of this constant depends upon the presence of the Servlet API on the classpath and can be + * overridden using the {@code "log4j2.isWebapp"} system property. + *

*/ - public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty( - "log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet")); + public static final boolean IS_WEB_APP = PropertiesUtil.getProperties() + .getBooleanProperty( + "log4j2.is.webapp", + isClassAvailable("javax.servlet.Servlet") || isClassAvailable("jakarta.servlet.Servlet")); /** - * Kill switch for object pooling in ThreadLocals that enables much of the LOG4J2-1270 no-GC behaviour. + * Specifies whether Log4j can bind non-JRE types to {@link ThreadLocal}s *

- * {@code True} for non-{@link #IS_WEB_APP web apps}, disable by setting system property - * "log4j2.enable.threadlocals" to "false". + * The value of this constant is {@code true}, unless Log4j is running in a servlet container (cf. + * {@link #IS_WEB_APP}). Use the {@code "log4j2.enableThreadlocals} system property to override its value. + *

+ *

+ * In order to enable the garbage-free behavior described in + * LOG4J2-1270, this constant must be {@code + * true}. + *

+ *

+ * Warning: This setting does not disable all thread locals. It only + * disables those thread locals that can cause a classloader memory leak. *

*/ - public static final boolean ENABLE_THREADLOCALS = !IS_WEB_APP && PropertiesUtil.getProperties().getBooleanProperty( - "log4j2.enable.threadlocals", true); + public static final boolean ENABLE_THREADLOCALS = + PropertiesUtil.getProperties().getBooleanProperty("log4j2.enable.threadlocals", !IS_WEB_APP); + /** + * Java major version. + * @since 2.8.1 + */ public static final int JAVA_MAJOR_VERSION = getMajorVersion(); /** * 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. + * The default value is 518, which allows the StringBuilder to resize three times from its initial size. * Users can override with system property "log4j.maxReusableMsgSize". *

- * @since 2.9 + * + * @since 2.9.0 */ 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 + * If system property {@value} is either defined empty or its value equals to {@code true} (ignoring case), 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}. *

+ * + * @since 2.9.0 */ public static final String LOG4J2_DEBUG = "log4j2.debug"; @@ -82,15 +114,30 @@ private static boolean isClassAvailable(final String className) { } } + /** + * The empty array. + * @since 2.15.0 + */ + public static final Object[] EMPTY_OBJECT_ARRAY = {}; + + /** + * The empty array. + * @since 2.15.0 + */ + public static final byte[] EMPTY_BYTE_ARRAY = {}; + /** * Prevent class instantiation. */ - private Constants() { - } + private Constants() {} private static int getMajorVersion() { - final String version = System.getProperty("java.version"); - final String[] parts = version.split("-|\\."); + return getMajorVersion(System.getProperty("java.version")); + } + + static int getMajorVersion(final String version) { + // Split into `major.minor.rest` + final String[] parts = version.split("-|\\.", 3); boolean isJEP223; try { final int token = Integer.parseInt(parts[0]); 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..c2a58032ae5 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 @@ -1,43 +1,43 @@ /* * 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.logging.log4j.util; -import java.util.Locale; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; /** * Consider this class private. - * + * *

* Helps convert English Strings to English Enum values. *

*

- * Enum name arguments are converted internally to upper case with the {@linkplain Locale#ENGLISH ENGLISH} locale to + * Enum name arguments are converted internally to upper case with the {@linkplain java.util.Locale#ENGLISH ENGLISH} locale to * avoid problems on the Turkish locale. Do not use with Turkish enum values. *

*/ +@InternalApi public final class EnglishEnums { - private EnglishEnums() { - } + private EnglishEnums() {} /** * Returns the Result for the given string. *

- * The {@code name} is converted internally to upper case with the {@linkplain Locale#ENGLISH ENGLISH} locale to + * The {@code name} is converted internally to upper case with the {@linkplain java.util.Locale#ENGLISH ENGLISH} locale to * avoid problems on the Turkish locale. Do not use with Turkish enum values. *

* @@ -53,7 +53,7 @@ public static > T valueOf(final Class enumType, final Strin /** * Returns an enum value for the given string. *

- * The {@code name} is converted internally to upper case with the {@linkplain Locale#ENGLISH ENGLISH} locale to + * The {@code name} is converted internally to upper case with the {@linkplain java.util.Locale#ENGLISH ENGLISH} locale to * avoid problems on the Turkish locale. Do not use with Turkish enum values. *

* @@ -64,7 +64,6 @@ public static > T valueOf(final Class enumType, final Strin * @return an enum value or {@code defaultValue} if {@code name} is null. */ public static > T valueOf(final Class enumType, final String name, final T defaultValue) { - return name == null ? defaultValue : Enum.valueOf(enumType, name.toUpperCase(Locale.ENGLISH)); + return name == null ? defaultValue : Enum.valueOf(enumType, toRootUpperCase(name)); } - } 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..c9edf087d8d 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 @@ -1,42 +1,81 @@ /* * 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.logging.log4j.util; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import java.util.Collection; import java.util.Map; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** - * 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 */ +@ServiceProvider(value = PropertySource.class, resolution = Resolution.OPTIONAL) public class EnvironmentPropertySource implements PropertySource { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String PREFIX = "LOG4J_"; + private static final int DEFAULT_PRIORITY = 100; + + private static final PropertySource INSTANCE = new EnvironmentPropertySource(); + + /** + * Method used by Java 9+ to instantiate providers + * @since 2.24.0 + * @see java.util.ServiceLoader + */ + public static PropertySource provider() { + return INSTANCE; + } + @Override public int getPriority() { - return -100; + return DEFAULT_PRIORITY; + } + + private static void logException(final SecurityException error) { + LOGGER.error("The environment variables are not available to Log4j due to security restrictions.", error); + } + + private static void logException(final SecurityException error, final String key) { + LOGGER.error("The environment variable {} is not available to Log4j due to security restrictions.", key, error); } @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 +83,44 @@ 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(final String key) { + try { + return System.getenv(key); + } catch (final SecurityException e) { + logException(e, key); + } + return PropertySource.super.getProperty(key); + } + + @Override + public boolean containsProperty(final String key) { + try { + return System.getenv().containsKey(key); + } catch (final SecurityException e) { + logException(e, key); + 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..d207e41949e 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 @@ -1,84 +1,71 @@ /* * 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.logging.log4j.util; +import static org.apache.logging.log4j.util.internal.SerializationUtil.REQUIRED_JAVA_CLASSES; +import static org.apache.logging.log4j.util.internal.SerializationUtil.REQUIRED_JAVA_PACKAGES; + import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.List; +import java.util.Collections; +import org.apache.logging.log4j.util.internal.SerializationUtil; /** - * Extended ObjectInputStream that only allows certain classes to be deserialized. + * Extends {@link ObjectInputStream} to only allow some built-in Log4j classes and caller-specified classes to be + * deserialized. * - * @since 2.8.2 + * @since 2.11.0 */ public class FilteredObjectInputStream extends ObjectInputStream { - private static final List REQUIRED_JAVA_CLASSES = Arrays.asList( - "java.math.BigDecimal", - "java.math.BigInteger", - // for Message delegate - "java.rmi.MarshalledObject", - "[B" - ); - - private static final List REQUIRED_JAVA_PACKAGES = Arrays.asList( - "java.lang.", - "java.time", - "java.util.", - "org.apache.logging.log4j.", - "[Lorg.apache.logging.log4j." - ); - - private final Collection allowedClasses; + private final Collection allowedExtraClasses; public FilteredObjectInputStream() throws IOException, SecurityException { - super(); - this.allowedClasses = new HashSet<>(); + this.allowedExtraClasses = Collections.emptySet(); } - public FilteredObjectInputStream(InputStream in) throws IOException { - super(in); - this.allowedClasses = new HashSet<>(); + public FilteredObjectInputStream(final InputStream inputStream) throws IOException { + super(inputStream); + this.allowedExtraClasses = Collections.emptySet(); } - public FilteredObjectInputStream(final Collection allowedClasses) throws IOException, SecurityException { - super(); - this.allowedClasses = allowedClasses; + public FilteredObjectInputStream(final Collection allowedExtraClasses) + throws IOException, SecurityException { + this.allowedExtraClasses = allowedExtraClasses; } - public FilteredObjectInputStream(final InputStream in, final Collection allowedClasses) throws IOException { - super(in); - this.allowedClasses = allowedClasses; + public FilteredObjectInputStream(final InputStream inputStream, final Collection allowedExtraClasses) + throws IOException { + super(inputStream); + this.allowedExtraClasses = allowedExtraClasses; } public Collection getAllowedClasses() { - return allowedClasses; + return allowedExtraClasses; } @Override protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { - String name = desc.getName(); - if (!(isAllowedByDefault(name) || allowedClasses.contains(name))) { + final String name = SerializationUtil.stripArray(desc.getName()); + if (!(isAllowedByDefault(name) || allowedExtraClasses.contains(name))) { throw new InvalidObjectException("Class is not allowed for deserialization: " + name); } return super.resolveClass(desc); @@ -96,5 +83,4 @@ private static boolean isRequiredPackage(final String name) { } return false; } - } 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..92dacc49cf5 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedStringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedStringMap.java index 7f5c0a9bb18..40fb2f3cc17 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedStringMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedStringMap.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; 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..46bab988bdf --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalApi.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.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 2.22.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..2c011d43430 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.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; + +/** + * Exception thrown when an error occurs while accessing internal resources. This is generally used to + * convert checked exceptions to runtime exceptions. + * + * @since 2.22.0 + */ +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..c02fd6c8d43 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 @@ -1,35 +1,33 @@ /* * 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.logging.log4j.util; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; - /** * Utility class for lambda support. + * @since 2.4 */ public final class LambdaUtil { /** * Private constructor: this class is not intended to be instantiated. */ - private LambdaUtil() { - } + private LambdaUtil() {} /** * Converts an array of lambda expressions into an array of their evaluation results. @@ -83,6 +81,7 @@ public static Message get(final MessageSupplier supplier) { * @param supplier a lambda expression or {@code null} * @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 + * @since 2.6 */ public static Message getMessage(final Supplier supplier, final MessageFactory messageFactory) { if (supplier == null) { 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..3af4ed46a01 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Lazy.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.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 + * @since 2.22.0 + */ +public interface Lazy extends Supplier { + /** + * Returns the value held by this lazy. This may cause the value to initialize if it hasn't been already. + */ + T value(); + + /** + * Returns the value held by this lazy. This may cause the value to initialize if it hasn't been already. + */ + @Override + default T get() { + return value(); + } + + /** + * Creates a new lazy value derived from this lazy value using the provided value mapping function. + */ + default Lazy map(final Function function) { + return lazy(() -> function.apply(value())); + } + + /** + * Indicates whether this lazy value has been initialized. + */ + boolean isInitialized(); + + /** + * Sets this lazy value to the provided value. + * + * @throws UnsupportedOperationException if this type of lazy value cannot be updated + */ + void set(final T newValue); + + /** + * Creates a strict lazy value using the provided Supplier. The supplier is guaranteed to only be invoked by at + * most one thread, and all threads will see the same published value when this returns. + */ + 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 lazy value using a weak reference to the provided value. + */ + static Lazy weak(final T value) { + return new LazyUtil.WeakConstant<>(value); + } + + /** + * 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..f287aae11c5 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyBoolean.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.util; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BooleanSupplier; + +/** + * @since 2.22.0 + */ +public class LazyBoolean implements BooleanSupplier { + private final BooleanSupplier supplier; + private final Lock lock = new ReentrantLock(); + 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) { + lock.lock(); + try { + uninitialized = !initialized; + if (uninitialized) { + this.value = value = supplier.getAsBoolean(); + initialized = true; + } + } finally { + lock.unlock(); + } + } + return value; + } + + public void setAsBoolean(final boolean b) { + lock.lock(); + try { + initialized = false; + value = b; + initialized = true; + } finally { + lock.unlock(); + } + } + + 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..8b0b8c7a297 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyUtil.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.util; + +import java.lang.ref.WeakReference; +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 WeakConstant implements Lazy { + private final WeakReference reference; + + WeakConstant(final T value) { + reference = new WeakReference<>(value); + } + + @Override + public T value() { + return reference.get(); + } + + @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 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..b326734ccb7 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 @@ -1,32 +1,34 @@ /* * 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.logging.log4j.util; import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; 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 java.util.function.Supplier; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** * Consider this class private. Utility class for ClassLoaders. @@ -35,9 +37,13 @@ * @see RuntimePermission * @see Thread#getContextClassLoader() * @see ClassLoader#getSystemClassLoader() + * @since 2.0.1 */ +@InternalApi public final class LoaderUtil { + private static final Logger LOGGER = StatusLogger.getLogger(); + /** * System property to set to ignore the thread context ClassLoader. * @@ -45,86 +51,127 @@ public final class LoaderUtil { */ public static final String IGNORE_TCCL_PROPERTY = "log4j.ignoreTCL"; - private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); - // this variable must be lazily loaded; otherwise, we get a nice circular class loading problem where LoaderUtil // wants to use PropertiesUtil, but then PropertiesUtil wants to use LoaderUtil. private static Boolean ignoreTCCL; - private static final boolean GET_CLASS_LOADER_DISABLED; - - private static final PrivilegedAction TCCL_GETTER = new ThreadContextClassLoaderGetter(); - - static { - if (SECURITY_MANAGER != null) { - boolean getClassLoaderDisabled; + static final RuntimePermission GET_CLASS_LOADER = new RuntimePermission("getClassLoader"); + static final LazyBoolean GET_CLASS_LOADER_DISABLED = new LazyBoolean(() -> { + if (System.getSecurityManager() == null) { + return false; + } + try { + AccessController.checkPermission(GET_CLASS_LOADER); + // seems like we'll be ok + return false; + } catch (final SecurityException ignored) { try { - SECURITY_MANAGER.checkPermission(new RuntimePermission("getClassLoader")); - getClassLoaderDisabled = false; - } catch (final SecurityException ignored) { - getClassLoaderDisabled = true; + // let's see if we can obtain that permission + AccessController.doPrivileged((PrivilegedAction) () -> { + AccessController.checkPermission(GET_CLASS_LOADER); + return null; + }); + return false; + } catch (final SecurityException ignore) { + // no chance + return true; } - GET_CLASS_LOADER_DISABLED = getClassLoaderDisabled; - } else { - GET_CLASS_LOADER_DISABLED = false; } + }); + + private static final PrivilegedAction TCCL_GETTER = new ThreadContextClassLoaderGetter(); + + private LoaderUtil() {} + + /** + * Returns the ClassLoader to use. + * + * @return the ClassLoader. + * @since 2.22.0 + */ + public static ClassLoader getClassLoader() { + return getClassLoader(LoaderUtil.class, null); } - private LoaderUtil() { + /** + * @since 2.22.0 + */ + // TODO: this method could use some explanation + public static ClassLoader getClassLoader(final Class class1, final Class class2) { + PrivilegedAction action = () -> { + final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader(); + final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader(); + final ClassLoader referenceLoader = GET_CLASS_LOADER_DISABLED.getAsBoolean() + ? getThisClassLoader() + : Thread.currentThread().getContextClassLoader(); + if (isChild(referenceLoader, loader1)) { + return isChild(referenceLoader, loader2) ? referenceLoader : loader2; + } + return isChild(loader1, loader2) ? loader1 : loader2; + }; + return runPrivileged(action); } /** - * 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 - * {@link SecurityManager} that does not allow access to the Thread ClassLoader or system ClassLoader, then the - * ClassLoader for this class is returned. + * 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. * - * @return the current ThreadContextClassLoader. + * @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. */ - public static ClassLoader getThreadContextClassLoader() { - if (GET_CLASS_LOADER_DISABLED) { - // we can at least get this class's ClassLoader regardless of security context - // however, if this is null, there's really no option left at this point - return LoaderUtil.class.getClassLoader(); + 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 SECURITY_MANAGER == null ? TCCL_GETTER.run() : AccessController.doPrivileged(TCCL_GETTER); + return loader1 != null; } /** + * Looks up the ClassLoader for this current thread. If this class does not have the runtime permission + * {@code getClassLoader}, then the only ClassLoader this attempts to look up is the loader behind this + * class. When a SecurityManager is installed, this attempts to make a privileged call to get the current + * {@linkplain Thread#getContextClassLoader() thread context ClassLoader}, falling back to either the + * ClassLoader of this class or the {@linkplain ClassLoader#getSystemClassLoader() system ClassLoader}. + * When no SecurityManager is present, the same lookups are performed without use of {@link AccessController}. + * If none of these strategies can obtain a ClassLoader, then this returns {@code null}. * + * @return the current thread's ClassLoader, a fallback loader, or null if no fallback can be determined */ + public static ClassLoader getThreadContextClassLoader() { + try { + return GET_CLASS_LOADER_DISABLED.getAsBoolean() ? getThisClassLoader() : runPrivileged(TCCL_GETTER); + } catch (final SecurityException ignored) { + return null; + } + } + + private static ClassLoader getThisClassLoader() { + return LoaderUtil.class.getClassLoader(); + } + + private static T runPrivileged(final PrivilegedAction action) { + return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run(); + } + private static class ThreadContextClassLoaderGetter implements PrivilegedAction { @Override public ClassLoader run() { - final ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl != null) { - return cl; + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + return contextClassLoader; } - final ClassLoader ccl = LoaderUtil.class.getClassLoader(); - return ccl == null && !GET_CLASS_LOADER_DISABLED ? ClassLoader.getSystemClassLoader() : ccl; - } - } - - 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 ClassLoader thisClassLoader = getThisClassLoader(); + if (thisClassLoader != null || GET_CLASS_LOADER_DISABLED.getAsBoolean()) { + return thisClassLoader; } + return ClassLoader.getSystemClassLoader(); } - ClassLoader parent = tcl; - while (parent != null && !classLoaders.contains(parent)) { - classLoaders.add(parent); - } - if (!classLoaders.contains(ClassLoader.getSystemClassLoader())) { - classLoaders.add(ClassLoader.getSystemClassLoader()); - } - return classLoaders.toArray(new ClassLoader[classLoaders.size()]); } /** @@ -136,12 +183,12 @@ public static ClassLoader[] getClassLoaders() { */ public static boolean isClassAvailable(final String className) { try { - final Class clazz = loadClass(className); - return clazz != null; + loadClass(className); + return true; } catch (final ClassNotFoundException | LinkageError e) { return false; - } catch (final Throwable e) { - LowLevelLogUtil.logException("Unknown error checking for existence of class: " + className, e); + } catch (final Throwable error) { + LOGGER.error("Unknown error while checking existence of class `{}`", className, error); return false; } } @@ -150,79 +197,144 @@ 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. * - * @param className The class name. - * @return the Class for the given name. - * @throws ClassNotFoundException if the specified class name could not be found + * @param className fully qualified class name to load + * @return the loaded class + * @throws ClassNotFoundException if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason * @since 2.1 */ public static Class loadClass(final String className) throws ClassNotFoundException { - if (isIgnoreTccl()) { - return Class.forName(className); + ClassLoader classLoader = isIgnoreTccl() ? getThisClassLoader() : getThreadContextClassLoader(); + if (classLoader == null) { + classLoader = getThisClassLoader(); } + return Class.forName(className, true, classLoader); + } + + /** + * Loads and initializes a class given its fully qualified class name. All checked reflective operation + * exceptions are translated into equivalent {@link LinkageError} classes. + * + * @param className fully qualified class name to load + * @return the loaded class + * @throws NoClassDefFoundError if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason + * @see #loadClass(String) + * @since 2.22.0 + */ + public static Class loadClassUnchecked(final String className) { try { - return getThreadContextClassLoader().loadClass(className); - } catch (final Throwable ignored) { - return Class.forName(className); + return loadClass(className); + } catch (final ClassNotFoundException e) { + final NoClassDefFoundError error = new NoClassDefFoundError(e.getMessage()); + error.initCause(e); + throw error; } } /** * 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 - * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws InvocationTargetException if there was an exception whilst constructing the class + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class * @since 2.7 */ public static T newInstanceOf(final Class clazz) - throws InstantiationException, IllegalAccessException, InvocationTargetException { + throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + final Constructor constructor = clazz.getDeclaredConstructor(); + return constructor.newInstance(); + } + + /** + * Creates an instance of the provided class using the default constructor. All checked reflective operation + * exceptions are translated into {@link LinkageError} or {@link InternalException}. + * + * @param clazz class to instantiate + * @param the type of the object being instantiated + * @return instance of the class + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @since 2.22.0 + */ + public static T newInstanceOfUnchecked(final Class clazz) { try { - return clazz.getConstructor().newInstance(); - } catch (final NoSuchMethodException ignored) { - // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above - return clazz.newInstance(); + return newInstanceOf(clazz); + } catch (final NoSuchMethodException e) { + final NoSuchMethodError error = new NoSuchMethodError(e.getMessage()); + error.initCause(e); + throw error; + } catch (final InvocationTargetException e) { + final Throwable cause = e.getCause(); + throw new InternalException(cause); + } catch (final InstantiationException e) { + final InstantiationError error = new InstantiationError(e.getMessage()); + error.initCause(e); + throw error; + } catch (final IllegalAccessException e) { + final IllegalAccessError error = new IllegalAccessError(e.getMessage()); + error.initCause(e); + throw error; } } /** * Loads and instantiates a Class using the default constructor. * - * @param className The class name. - * @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 + * @param className fully qualified class name to load, initialize, and construct + * @param type the class must be compatible with + * @return new instance of the class + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor * @since 2.1 */ - @SuppressWarnings("unchecked") - public static T newInstanceOf(final String className) throws ClassNotFoundException, IllegalAccessException, - InstantiationException, NoSuchMethodException, InvocationTargetException { - return newInstanceOf((Class) loadClass(className)); + public static T newInstanceOf(final String className) + throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, + NoSuchMethodException { + final Class clazz = Cast.cast(loadClass(className)); + return newInstanceOf(clazz); } /** - * Loads and instantiates a derived class using its default constructor. + * Loads and instantiates a class given by a property name. * - * @param className The class name. - * @param clazz The class to cast it to. - * @param The type of the class to check. - * @return new instance of the class cast to {@code T} - * @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 + * @param propertyName The property name to look up a class name for. + * @param clazz The class to cast it to. + * @param The type to cast it to. + * @return new instance of the class given in the property or {@code null} if the property was unset. + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor + * @since 2.5 */ - public static T newCheckedInstanceOf(final String className, final Class clazz) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { - return clazz.cast(newInstanceOf(className)); + public static T newCheckedInstanceOfProperty(final String propertyName, final Class clazz) + throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, + NoSuchMethodException { + return newCheckedInstanceOfProperty(propertyName, clazz, () -> null); } /** @@ -230,26 +342,102 @@ public static T newCheckedInstanceOf(final String className, final Class * * @param propertyName The property name to look up a class name for. * @param clazz The class to cast it to. + * @param defaultSupplier Supplier of a default value if the property is not present. * @param The type to cast it to. * @return new instance of the class given in the property or {@code null} if the property was unset. - * @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 + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor + * @since 2.22.0 */ - public static T newCheckedInstanceOfProperty(final String propertyName, final Class clazz) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, - IllegalAccessException { + public static T newCheckedInstanceOfProperty( + final String propertyName, final Class clazz, final Supplier defaultSupplier) + throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, + NoSuchMethodException { final String className = PropertiesUtil.getProperties().getStringProperty(propertyName); if (className == null) { - return null; + return defaultSupplier.get(); } return newCheckedInstanceOf(className, clazz); } + /** + * Loads and instantiates a class by name using its default constructor. All checked reflective operation + * exceptions are translated into corresponding {@link LinkageError} classes. + * + * @param className fully qualified class name to load, initialize, and construct + * @param type the class must be compatible with + * @return new instance of the class + * @throws NoClassDefFoundError if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @throws LinkageError if the linkage of the class fails for any other reason + * @since 2.22.0 + */ + public static T newInstanceOfUnchecked(final String className) { + final Class clazz = Cast.cast(loadClassUnchecked(className)); + return newInstanceOfUnchecked(clazz); + } + + /** + * Loads and instantiates a derived class using its default constructor. + * + * @param className The class name. + * @param clazz The class to cast it to. + * @param The type of the class to check. + * @return new instance of the class cast to {@code T} + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the constructed object isn't type compatible with {@code T} + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if there was an exception whilst constructing the class + * @since 2.1 + */ + public static T newCheckedInstanceOf(final String className, final Class clazz) + throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, + NoSuchMethodException { + return newInstanceOf(loadClass(className).asSubclass(clazz)); + } + + /** + * Loads the provided class by name as a checked subtype of the given class. All checked reflective operation + * exceptions are translated into corresponding {@link LinkageError} classes. + * + * @param className fully qualified class name to load + * @param supertype supertype of the class being loaded + * @param type of instance to return + * @return new instance of the requested class + * @throws NoClassDefFoundError if the provided class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws ClassCastException if the loaded class is not a subtype of the provided class + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @throws LinkageError if the linkage of the class fails for any other reason + * @since 2.22.0 + */ + public static T newInstanceOfUnchecked(final String className, final Class supertype) { + final Class clazz = loadClassUnchecked(className).asSubclass(supertype); + return newInstanceOfUnchecked(clazz); + } + private static boolean isIgnoreTccl() { // we need to lazily initialize this, but concurrent access is not an issue if (ignoreTCCL == null) { @@ -267,7 +455,11 @@ private static boolean isIgnoreTccl() { * @since 2.1 */ public static Collection findResources(final String resource) { - final Collection urlResources = findUrlResources(resource); + return findResources(resource, true); + } + + 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 +467,13 @@ public static Collection findResources(final String resource) { return resources; } - static Collection findUrlResources(final String resource) { + static Collection findUrlResources(final String resource, final boolean useTccl) { // @formatter:off final ClassLoader[] candidates = { - getThreadContextClassLoader(), - LoaderUtil.class.getClassLoader(), - GET_CLASS_LOADER_DISABLED ? null : ClassLoader.getSystemClassLoader()}; + useTccl ? getThreadContextClassLoader() : null, + LoaderUtil.class.getClassLoader(), + GET_CLASS_LOADER_DISABLED.getAsBoolean() ? null : ClassLoader.getSystemClassLoader() + }; // @formatter:on final Collection resources = new LinkedHashSet<>(); for (final ClassLoader cl : candidates) { @@ -290,8 +483,8 @@ static Collection findUrlResources(final String resource) { while (resourceEnum.hasMoreElements()) { resources.add(new UrlResource(cl, resourceEnum.nextElement())); } - } catch (final IOException e) { - LowLevelLogUtil.logException(e); + } catch (final IOException error) { + LOGGER.error("failed to collect resources of name `{}`", resource, error); } } } @@ -323,20 +516,13 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof UrlResource)) { return false; } final UrlResource that = (UrlResource) 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 Objects.equals(classLoader, that.classLoader) && Objects.equals(url, that.url); } @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 deleted file mode 100644 index 5cf40cabee6..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.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.util; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.Objects; - -/** - * PrintWriter-based logging utility for classes too low level to use {@link org.apache.logging.log4j.status.StatusLogger}. - * Such classes cannot use StatusLogger as StatusLogger or {@link org.apache.logging.log4j.simple.SimpleLogger} depends - * on them for initialization. Other framework classes should stick to using StatusLogger. - * - * @since 2.6 - */ -final class LowLevelLogUtil { - - private static PrintWriter writer = new PrintWriter(System.err, true); - - /** - * 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); - } - } - - public static void logException(final Throwable exception) { - if (exception != null) { - exception.printStackTrace(writer); - } - } - - 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); - } - - 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..d3776a1a744 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.util; import org.apache.logging.log4j.message.Message; @@ -34,12 +33,14 @@ * * @since 2.4 */ -public interface MessageSupplier { +@FunctionalInterface +public interface MessageSupplier extends Supplier { /** * Gets a Message. * * @return a Message */ + @Override Message get(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/MultiFormatStringBuilderFormattable.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/MultiFormatStringBuilderFormattable.java index 46071856ed2..570d655edb1 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/MultiFormatStringBuilderFormattable.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/MultiFormatStringBuilderFormattable.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -37,5 +37,4 @@ public interface MultiFormatStringBuilderFormattable extends MultiformatMessage, * @param buffer the StringBuilder to write into */ void formatTo(String[] formats, StringBuilder buffer); - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java new file mode 100644 index 00000000000..26eeae17185 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/OsgiServiceLocator.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Objects; +import java.util.stream.Stream; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.wiring.BundleRevision; + +/** + * @since 2.18.0 + */ +public class OsgiServiceLocator { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private static final boolean OSGI_AVAILABLE = checkOsgiAvailable(); + + private static boolean checkOsgiAvailable() { + try { + /* + * OSGI classes of any version can still be present even if Log4j2 does not run in + * an OSGI container, hence we check if this class is in a bundle. + */ + final Class clazz = Class.forName("org.osgi.framework.FrameworkUtil"); + return clazz.getMethod("getBundle", Class.class).invoke(null, OsgiServiceLocator.class) != null; + } catch (final ClassNotFoundException | NoSuchMethodException | LinkageError e) { + return false; + } catch (final Throwable error) { + LOGGER.error("Unknown error checking OSGI environment.", error); + return false; + } + } + + public static boolean isAvailable() { + return OSGI_AVAILABLE; + } + + public static Stream loadServices(final Class serviceType, final Lookup lookup) { + return loadServices(serviceType, lookup, true); + } + + public static Stream loadServices(final Class serviceType, final Lookup lookup, final boolean verbose) { + final Class lookupClass = Objects.requireNonNull(lookup, "lookup").lookupClass(); + return loadServices(serviceType, lookupClass, StatusLogger.getLogger()); + } + + static Stream loadServices(final Class serviceType, final Class callerClass, final Logger logger) { + final Bundle bundle = FrameworkUtil.getBundle(callerClass); + if (bundle != null && !isFragment(bundle)) { + final BundleContext ctx = bundle.getBundleContext(); + if (ctx == null) { + logger.warn( + "Unable to load OSGi services for service {}: bundle {} (state {}) does not have a valid BundleContext", + serviceType::getName, + bundle::getSymbolicName, + () -> { + switch (bundle.getState()) { + case Bundle.UNINSTALLED: + return "UNINSTALLED"; + case Bundle.INSTALLED: + return "INSTALLED"; + case Bundle.RESOLVED: + return "RESOLVED"; + case Bundle.STARTING: + return "STARTING"; + case Bundle.STOPPING: + return "STOPPING"; + case Bundle.ACTIVE: + return "ACTIVE"; + default: + return "UNKNOWN"; + } + }); + + } else { + try { + return ctx.getServiceReferences(serviceType, null).stream().map(ctx::getService); + } catch (final Exception e) { + logger.error("Unable to load OSGI services for service {}", serviceType, e); + } + } + } + return Stream.empty(); + } + + private static boolean isFragment(final Bundle bundle) { + try { + return (bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0; + } catch (final SecurityException ignored) { + return false; + } + } +} 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..ccfac6b04ca 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.util; import java.lang.annotation.Retention; 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..7f9581345b9 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.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.util; + +import java.util.ArrayDeque; +import java.util.Collections; +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 candidate = createPrivateSecurityManager(); + if (isCapable(candidate)) { + SECURITY_MANAGER = candidate; + } else { + SECURITY_MANAGER = null; + } + } + + private static boolean isCapable(PrivateSecurityManager candidate) { + if (candidate == null) { + return false; + } + + try { + final Class[] result = candidate.getClassContext(); + if (result == null || result.length == 0) { + // This happens e.g. on Android which has real implementation of SecurityManager replaced with merely + // stubs. So the PrivateSecurityManager, though can be instantiated, will not produce meaningful + // results + return false; + } + // Add more checks here as needed + return true; + } catch (Exception ignored) { + return false; + } + } + + private static PrivateSecurityManager createPrivateSecurityManager() { + 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; + } + + return 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); + Collections.addAll(classes, array); + 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..a1be559a565 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -21,7 +21,9 @@ import java.lang.reflect.Method; /** - * @Since 2.9 + * Provides the PID of the current JVM. + * + * @since 2.9.0 */ public class ProcessIdUtil { @@ -29,16 +31,17 @@ public class ProcessIdUtil { 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"); + // LOG4J2-2126 use reflection to improve compatibility with Android Platform which does not support JMX + // extensions + final Class managementFactoryClass = Class.forName("java.lang.management.ManagementFactory"); + final Method getRuntimeMXBean = managementFactoryClass.getDeclaredMethod("getRuntimeMXBean"); + final Class runtimeMXBeanClass = Class.forName("java.lang.management.RuntimeMXBean"); + final 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 + final Object runtimeMXBean = getRuntimeMXBean.invoke(null); + final String name = (String) getName.invoke(runtimeMXBean); + // Split into first@rest + return name.split("@", 2)[0]; // likely works on most platforms } catch (final Exception ex) { try { return new File("/proc/self").getCanonicalFile().getName(); // try a Linux-specific way 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..8ff065ecce9 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.util; +import java.util.Collection; import java.util.Map; import java.util.Properties; @@ -27,17 +28,27 @@ */ 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); + } + + /** + * @since 2.18.0 + */ + 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 +60,22 @@ 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(final String key) { + return properties.getProperty(key); + } + + @Override + public boolean containsProperty(final 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..145216b3332 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 @@ -1,34 +1,47 @@ /* * 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.logging.log4j.util; +import aQute.bnd.annotation.Cardinality; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceConsumer; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; +import java.util.Collection; 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.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** * Consider this class private. @@ -43,10 +56,19 @@ * * @see PropertySource */ +@ServiceConsumer(value = PropertySource.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE) public final class PropertiesUtil { + private static final Logger LOGGER = StatusLogger.getLogger(); + 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, false)); + + private static final Pattern DURATION_PATTERN = Pattern.compile("([+-]?\\d+)\\s*(\\w+)?", Pattern.CASE_INSENSITIVE); private final Environment environment; @@ -56,7 +78,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,7 +88,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(new PropertyFilePropertySource(propertiesFileName, useTccl)); + } + + /** + * Constructs a PropertiesUtil for a give property source as source of additional properties. + * @param source a property source + */ + PropertiesUtil(final PropertySource source) { + environment = new Environment(source); } /** @@ -81,13 +115,13 @@ static Properties loadClose(final InputStream in, final Object source) { if (null != in) { try { props.load(in); - } catch (final IOException e) { - LowLevelLogUtil.logException("Unable to read " + source, e); + } catch (final IOException error) { + LOGGER.error("Unable to read source `{}`", source, error); } finally { try { in.close(); - } catch (final IOException e) { - LowLevelLogUtil.logException("Unable to close " + source, e); + } catch (final IOException error) { + LOGGER.error("Unable to close source `{}`", source, error); } } } @@ -100,7 +134,25 @@ static Properties loadClose(final InputStream in, final Object source) { * @return the main Log4j PropertiesUtil instance. */ public static PropertiesUtil getProperties() { - return LOG4J_PROPERTIES; + return COMPONENT_PROPERTIES.get(); + } + + /** + * Allows a {@link PropertySource} to be added after {@code PropertiesUtil} has been created. + * @param propertySource the {@code PropertySource} to add. + * @since 2.19.0 + */ + public void addPropertySource(final PropertySource propertySource) { + environment.addPropertySource(Objects.requireNonNull(propertySource)); + } + + /** + * Removes a {@link PropertySource}. + * @param propertySource the {@code PropertySource} to remove. + * @since 2.24.0 + */ + public void removePropertySource(final PropertySource propertySource) { + environment.removePropertySource(Objects.requireNonNull(propertySource)); } /** @@ -108,6 +160,7 @@ public static PropertiesUtil getProperties() { * * @param name the name of the property to verify * @return {@code true} if the specified property is defined, regardless of its value + * @since 2.9.0 */ public boolean hasProperty(final String name) { return environment.containsKey(name); @@ -144,12 +197,32 @@ public boolean getBooleanProperty(final String name, final boolean defaultValue) * @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. + * @since 2.9.0 */ - public boolean getBooleanProperty(final String name, final boolean defaultValueIfAbsent, - final boolean defaultValueIfPresent) { + 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); + 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 + */ + public Boolean getBooleanProperty(final String[] prefixes, final String key, final Supplier supplier) { + for (final String prefix : prefixes) { + if (hasProperty(prefix + key)) { + return getBooleanProperty(prefix + key); + } + } + return supplier != null ? supplier.get() : null; } /** @@ -157,6 +230,7 @@ public boolean getBooleanProperty(final String name, final boolean defaultValueI * * @param name the name of the property to look up * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. + * @since 2.8 */ public Charset getCharsetProperty(final String name) { return getCharsetProperty(name, Charset.defaultCharset()); @@ -169,6 +243,7 @@ public Charset getCharsetProperty(final String name) { * @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. + * @since 2.8 */ public Charset getCharsetProperty(final String name, final Charset defaultValue) { final String charsetName = getStringProperty(name); @@ -178,15 +253,18 @@ public Charset getCharsetProperty(final String name, final Charset defaultValue) if (Charset.isSupported(charsetName)) { return Charset.forName(charsetName); } - ResourceBundle bundle = getCharsetsResourceBundle(); + final ResourceBundle bundle = getCharsetsResourceBundle(); if (bundle.containsKey(name)) { - String mapped = bundle.getString(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."); + LOGGER.warn( + "Unable to read charset `{}` from property `{}`. Falling back to the default: `{}`", + charsetName, + name, + defaultValue); return defaultValue; } @@ -196,14 +274,20 @@ public Charset getCharsetProperty(final String name, final Charset defaultValue) * @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 double value of the property or {@code defaultValue} if it was undefined or could not be parsed. + * @since 2.6 */ public double getDoubleProperty(final String name, final double defaultValue) { final String prop = getStringProperty(name); if (prop != null) { try { return Double.parseDouble(prop); - } catch (final Exception ignored) { - return defaultValue; + } catch (final NumberFormatException e) { + LOGGER.warn( + "Unable to read double `{}` from property `{}`. Falling back to the default: `{}`", + prop, + name, + defaultValue, + e); } } return defaultValue; @@ -221,14 +305,37 @@ 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 Integer.parseInt(prop.trim()); + } catch (final NumberFormatException e) { + LOGGER.warn( + "Unable to read int `{}` from property `{}`. Falling back to the default: `{}`", + prop, + name, + defaultValue, + e); } } 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 + */ + public Integer getIntegerProperty(final String[] prefixes, final String key, final 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. * @@ -241,13 +348,97 @@ public long getLongProperty(final String name, final long defaultValue) { if (prop != null) { try { return Long.parseLong(prop); - } catch (final Exception ignored) { - return defaultValue; + } catch (final NumberFormatException e) { + LOGGER.warn( + "Unable to read long `{}` from property `{}`. Falling back to the default: `{}`", + prop, + name, + defaultValue, + e); } } 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 + */ + public Long getLongProperty(final String[] prefixes, final String key, final 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 + */ + public Duration getDurationProperty(final String name, final Duration defaultValue) { + final String prop = getStringProperty(name); + try { + return parseDuration(prop); + } catch (final IllegalArgumentException e) { + LOGGER.warn( + "Unable to read duration `{}` from property `{}`.\nExpected format 'n unit', where 'n' is an " + + "integer and 'unit' is one of: {}.", + prop, + name, + TimeUnit.getValidUnits().collect(Collectors.joining(", ")), + e); + } + 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 + */ + public Duration getDurationProperty(final String[] prefixes, final String key, final 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 + */ + public String getStringProperty(final String[] prefixes, final String key, final 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. * @@ -267,7 +458,7 @@ public String getStringProperty(final String name) { */ public String getStringProperty(final String name, final String defaultValue) { final String prop = getStringProperty(name); - return (prop == null) ? defaultValue : prop; + return prop == null ? defaultValue : prop; } /** @@ -278,8 +469,8 @@ public String getStringProperty(final String name, final String defaultValue) { public static Properties getSystemProperties() { try { return new Properties(System.getProperties()); - } catch (final SecurityException ex) { - LowLevelLogUtil.logException("Unable to access system properties.", ex); + } catch (final SecurityException error) { + LOGGER.error("Unable to access system properties.", error); // Sandboxed - can't read System Properties return new Properties(); } @@ -289,10 +480,10 @@ public static Properties getSystemProperties() { * Reloads all properties. This is primarily useful for unit tests. * * @since 2.10.0 + * @deprecated since 2.24.0 caching of property values is disabled. */ - public void reload() { - environment.reload(); - } + @Deprecated + public void reload() {} /** * Provides support for looking up global configuration properties via environment variables, property files, @@ -307,68 +498,99 @@ public void reload() { * * @since 2.10.0 */ - private static class Environment { + private static final class Environment { - private final Set sources = new TreeSet<>(new PropertySource.Comparator()); - private final Map literal = new ConcurrentHashMap<>(); - private final Map normalized = new ConcurrentHashMap<>(); - private final Map, String> tokenized = new ConcurrentHashMap<>(); + private final Set sources = ConcurrentHashMap.newKeySet(); + private final ThreadLocal CURRENT_PROPERTY_SOURCE = new ThreadLocal<>(); private Environment(final PropertySource propertySource) { - sources.add(propertySource); - for (final PropertySource source : ServiceLoader.load(PropertySource.class)) { - sources.add(source); - } - reload(); - } - - 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); - } + final PropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME, false); + try { + sysProps.forEach((key, value) -> { + if (System.getProperty(key) == null) { + System.setProperty(key, value); } }); + } catch (final SecurityException e) { + LOGGER.warn( + "Unable to set Java system properties from {} file, due to security restrictions.", + LOG4J_SYSTEM_PROPERTIES_FILE_NAME, + e); } + sources.add(propertySource); + // We don't log anything on the status logger. + ServiceLoaderUtil.safeStream( + PropertySource.class, + ServiceLoader.load(PropertySource.class, PropertiesUtil.class.getClassLoader()), + LOGGER) + .forEach(sources::add); } - private static boolean hasSystemProperty(final String key) { - try { - return System.getProperties().containsKey(key); - } catch (final SecurityException ignored) { - return false; - } + private void addPropertySource(final PropertySource propertySource) { + sources.add(propertySource); + } + + private void removePropertySource(final PropertySource propertySource) { + sources.remove(propertySource); } private String get(final String key) { - if (normalized.containsKey(key)) { - return normalized.get(key); - } - if (literal.containsKey(key)) { - return literal.get(key); + final List tokens = PropertySource.Util.tokenize(key); + return sources.stream() + .sorted(PropertySource.Comparator.INSTANCE) + .map(source -> { + if (!tokens.isEmpty()) { + final String normalKey = Objects.toString(source.getNormalForm(tokens), null); + if (normalKey != null && sourceContainsProperty(source, normalKey)) { + return sourceGetProperty(source, normalKey); + } + } + return sourceGetProperty(source, key); + }) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private boolean sourceContainsProperty(final PropertySource source, final String key) { + PropertySource recursiveSource = CURRENT_PROPERTY_SOURCE.get(); + if (recursiveSource == null) { + CURRENT_PROPERTY_SOURCE.set(source); + try { + return source.containsProperty(key); + } catch (final Exception e) { + LOGGER.warn("Failed to retrieve Log4j property {} from property source {}.", key, source, e); + } finally { + CURRENT_PROPERTY_SOURCE.remove(); + } } - if (hasSystemProperty(key)) { - return System.getProperty(key); + LOGGER.warn("Recursive call to `containsProperty()` from property source {}.", recursiveSource); + return false; + } + + private String sourceGetProperty(final PropertySource source, final String key) { + PropertySource recursiveSource = CURRENT_PROPERTY_SOURCE.get(); + if (recursiveSource == null) { + CURRENT_PROPERTY_SOURCE.set(source); + try { + return source.getProperty(key); + } catch (final Exception e) { + LOGGER.warn("Failed to retrieve Log4j property {} from property source {}.", key, source, e); + } finally { + CURRENT_PROPERTY_SOURCE.remove(); + } } - return tokenized.get(PropertySource.Util.tokenize(key)); + LOGGER.warn("Recursive call to `getProperty()` from property source {}.", recursiveSource); + return null; } private boolean containsKey(final String key) { - return normalized.containsKey(key) || - literal.containsKey(key) || - hasSystemProperty(key) || - tokenized.containsKey(PropertySource.Util.tokenize(key)); + final List tokens = PropertySource.Util.tokenize(key); + return sources.stream().anyMatch(s -> { + final CharSequence normalizedKey = tokens.isEmpty() ? null : s.getNormalForm(tokens); + return sourceContainsProperty(s, key) + || (normalizedKey != null && sourceContainsProperty(s, normalizedKey.toString())); + }); } } @@ -379,17 +601,18 @@ private boolean containsKey(final String key) { * @param properties The Properties to evaluate. * @param prefix The prefix to extract. * @return The subset of properties. + * @since 2.4 */ public static Properties extractSubset(final Properties properties, final String prefix) { final Properties subset = new Properties(); - if (prefix == null || prefix.length() == 0) { + if (prefix == null || prefix.isEmpty()) { return subset; } final String prefixToMatch = prefix.charAt(prefix.length() - 1) != '.' ? prefix + '.' : prefix; - final List keys = new ArrayList<>(); + final Collection keys = new ArrayList<>(); for (final String key : properties.stringPropertyNames()) { if (key.startsWith(prefixToMatch)) { @@ -417,13 +640,37 @@ 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; } @@ -432,9 +679,61 @@ public static Map partitionOnCommonPrefixes(final Properties * Returns true if system properties tell us we are running on Windows. * * @return true if system properties tell us we are running on Windows. + * @since 2.5 */ public boolean isOsWindows() { - return getStringProperty("os.name", "").startsWith("Windows"); + return SystemPropertiesPropertySource.getSystemProperty("os.name", "").startsWith("Windows"); } + static Duration parseDuration(final CharSequence value) { + final Matcher matcher = DURATION_PATTERN.matcher(value); + if (matcher.matches()) { + return Duration.of(parseDurationAmount(matcher.group(1)), TimeUnit.parseUnit(matcher.group(2))); + } + throw new IllegalArgumentException("Invalid duration value '" + value + "'."); + } + + private static long parseDurationAmount(final String amount) { + try { + return Long.parseLong(amount); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException("Invalid duration amount '" + amount + "'", e); + } + } + + private enum TimeUnit { + NANOS(new String[] {"ns", "nano", "nanos", "nanosecond", "nanoseconds"}, ChronoUnit.NANOS), + MICROS(new String[] {"us", "micro", "micros", "microsecond", "microseconds"}, ChronoUnit.MICROS), + MILLIS(new String[] {"ms", "milli", "millis", "millisecond", "milliseconds"}, ChronoUnit.MILLIS), + SECONDS(new String[] {"s", "second", "seconds"}, ChronoUnit.SECONDS), + MINUTES(new String[] {"m", "minute", "minutes"}, ChronoUnit.MINUTES), + HOURS(new String[] {"h", "hour", "hours"}, ChronoUnit.HOURS), + DAYS(new String[] {"d", "day", "days"}, ChronoUnit.DAYS); + + private final String[] descriptions; + private final TemporalUnit timeUnit; + + TimeUnit(final String[] descriptions, final TemporalUnit timeUnit) { + this.descriptions = descriptions; + this.timeUnit = timeUnit; + } + + private static Stream getValidUnits() { + return Arrays.stream(values()).flatMap(unit -> Arrays.stream(unit.descriptions)); + } + + private static TemporalUnit parseUnit(final String unit) { + if (unit != null) { + for (final TimeUnit value : values()) { + for (final String description : value.descriptions) { + if (unit.equals(description)) { + return value.timeUnit; + } + } + } + throw new IllegalArgumentException("Invalid duration unit '" + unit + "'"); + } + return ChronoUnit.MILLIS; + } + } } 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..acb4310c3c3 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 @@ -1,25 +1,28 @@ /* * 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.logging.log4j.util; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Properties; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** * PropertySource backed by a properties file. Follows the same conventions as {@link PropertiesPropertySource}. @@ -28,25 +31,31 @@ */ public class PropertyFilePropertySource extends PropertiesPropertySource { + private static final Logger LOGGER = StatusLogger.getLogger(); + public PropertyFilePropertySource(final String fileName) { - super(loadPropertiesFile(fileName)); + this(fileName, true); + } + + /** + * @since 2.18.0 + */ + public PropertyFilePropertySource(final String fileName, final boolean useTccl) { + super(loadPropertiesFile(fileName, useTccl)); } - private static Properties loadPropertiesFile(final String fileName) { + @SuppressFBWarnings( + value = "URLCONNECTION_SSRF_FD", + justification = "This property source should only be used with hardcoded file names.") + 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) { - LowLevelLogUtil.logException("Unable to read " + url, e); + } catch (final IOException error) { + LOGGER.error("Unable to read URL `{}`", url, error); } } 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..63c8173c443 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 @@ -1,23 +1,28 @@ /* * 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.logging.log4j.util; +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; + 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 +39,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 +50,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 + * @since 2.18.0 + */ + 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 +69,29 @@ 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. @@ -64,9 +101,13 @@ public interface PropertySource { class Comparator implements java.util.Comparator, Serializable { private static final long serialVersionUID = 1L; + static final Comparator INSTANCE = new Comparator(); + @Override public int compare(final PropertySource o1, final PropertySource o2) { - return Integer.compare(Objects.requireNonNull(o1).getPriority(), Objects.requireNonNull(o2).getPriority()); + return Integer.compare( + Objects.requireNonNull(o1).getPriority(), + Objects.requireNonNull(o2).getPriority()); } } @@ -76,10 +117,21 @@ 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}, * {@code log4j2}, {@code Log4j}, or {@code org.apache.logging.log4j}, along with separators of @@ -89,14 +141,23 @@ final class Util { * @param value property name * @return the property broken into lower case tokens */ + // https://errorprone.info/bugpattern/CollectionUndefinedEquality + @SuppressWarnings("CollectionUndefinedEquality") public static List tokenize(final CharSequence value) { - if (CACHE.containsKey(value)) { - return CACHE.get(value); + // `value` should be a `String` + if (CACHE.containsKey(value.toString())) { + return CACHE.get(value.toString()); } - 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(toRootLowerCase(matcher.group(1))); + start = matcher.end(); + } } CACHE.put(value, tokens); return tokens; @@ -125,7 +186,6 @@ public static CharSequence joinAsCamelCase(final Iterable providerRegistration = null; + + protected ProviderActivator(final Provider provider) { + this.provider = provider; + } + + @Override + @SuppressWarnings("JdkObsolete") + public void start(final BundleContext context) throws Exception { + final Hashtable props = new Hashtable<>(); + props.put(API_VERSION, provider.getVersions()); + providerRegistration = context.registerService(Provider.class, provider, props); + } + + @Override + public void stop(final BundleContext context) throws Exception { + if (providerRegistration != null) { + providerRegistration.unregister(); + } + } +} 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 index 200a6f320a6..2b74606f081 100644 --- 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 @@ -1,83 +1,90 @@ /* * 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.logging.log4j.util; +import static org.apache.logging.log4j.spi.Provider.PROVIDER_PROPERTY_NAME; + +import aQute.bnd.annotation.Cardinality; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceConsumer; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; import java.util.Collection; +import java.util.Comparator; 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 java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.simple.internal.SimpleProvider; +import org.apache.logging.log4j.spi.LoggerContextFactory; 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. + * 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. + *

*/ +@InternalApi +@ServiceConsumer(value = Provider.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE) public final class ProviderUtil { /** * Resource name for a Log4j 2 provider properties file. */ - protected static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties"; + static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties"; /** * Loaded providers. */ - protected static final Collection PROVIDERS = new HashSet<>(); + static final Collection PROVIDERS = new HashSet<>(); /** - * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support. - * - * @since 2.1 + * Guards the ProviderUtil singleton instance from lazy initialization. + *

+ * This is primarily used for OSGi support. It allows the OSGi Activator to pause the startup and wait for a + * Provider to be installed. See LOG4J2-373. + *

*/ - protected static final Lock STARTUP_LOCK = new ReentrantLock(); + 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 static volatile Provider PROVIDER; - 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()); - } - } + private ProviderUtil() {} - protected static void addProvider(final Provider provider) { - PROVIDERS.add(provider); - LOGGER.debug("Loaded Provider {}", provider); + static void addProvider(final Provider provider) { + if (validVersion(provider.getVersions())) { + PROVIDERS.add(provider); + LOGGER.debug("Loaded provider:\n{}", provider); + } else { + LOGGER.warn("Ignoring provider for incompatible version {}:\n{}", provider.getVersions(), provider); + } } /** @@ -87,33 +94,24 @@ protected static void addProvider(final Provider provider) { * @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) { + @SuppressFBWarnings( + value = "URLCONNECTION_SSRF_FD", + justification = "Uses a fixed URL that ends in 'META-INF/log4j-provider.properties'.") + @SuppressWarnings("deprecation") + 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); - } + addProvider(new Provider(props, url, cl)); } 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) { + static void loadProviders(final Enumeration urls, final ClassLoader cl) { if (urls != null) { while (urls.hasMoreElements()) { loadProvider(urls.nextElement(), cl); @@ -121,6 +119,14 @@ protected static void loadProviders(final Enumeration urls, final ClassLoad } } + /** + * @since 2.24.0 + */ + public static Provider getProvider() { + lazyInit(); + return PROVIDER; + } + public static Iterable getProviders() { lazyInit(); return PROVIDERS; @@ -133,17 +139,29 @@ public static boolean hasProviders() { /** * Lazily initializes the ProviderUtil singleton. - * - * @since 2.1 + *

+ * Note that the following initial call to ProviderUtil may block until a Provider has been installed when + * running in an OSGi environment. + *

*/ - protected static void lazyInit() { - // noinspection DoubleCheckedLocking - if (instance == null) { + static void lazyInit() { + if (PROVIDER == null) { try { STARTUP_LOCK.lockInterruptibly(); try { - if (instance == null) { - instance = new ProviderUtil(); + if (PROVIDER == null) { + ServiceLoaderUtil.safeStream( + Provider.class, + ServiceLoader.load(Provider.class, ProviderUtil.class.getClassLoader()), + LOGGER) + .filter(provider -> validVersion(provider.getVersions())) + .forEach(ProviderUtil::addProvider); + + for (final LoaderUtil.UrlResource resource : + LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) { + loadProvider(resource.getUrl(), resource.getClassLoader()); + } + PROVIDER = selectProvider(PropertiesUtil.getProperties(), PROVIDERS, LOGGER); } } finally { STARTUP_LOCK.unlock(); @@ -155,6 +173,95 @@ protected static void lazyInit() { } } + /** + * Used to test the public {@link #getProvider()} method. + */ + @SuppressWarnings("deprecation") + static Provider selectProvider( + final PropertiesUtil properties, final Collection providers, final Logger statusLogger) { + Provider selected = null; + // 1. Select provider using "log4j.provider" property + final String providerClass = properties.getStringProperty(PROVIDER_PROPERTY_NAME); + if (providerClass != null) { + if (SimpleProvider.class.getName().equals(providerClass)) { + selected = new SimpleProvider(); + } else { + try { + selected = LoaderUtil.newInstanceOf(providerClass); + } catch (final Exception e) { + statusLogger.error( + "Unable to create provider {}.\nFalling back to default selection process.", PROVIDER, e); + } + } + } + // 2. Use deprecated "log4j2.loggerContextFactory" property to choose the provider + final String factoryClassName = properties.getStringProperty(LogManager.FACTORY_PROPERTY_NAME); + if (factoryClassName != null) { + if (selected != null) { + statusLogger.warn( + "Ignoring {} system property, since {} was set.", + LogManager.FACTORY_PROPERTY_NAME, + PROVIDER_PROPERTY_NAME); + // 2a. Scan the known providers for one matching the logger context factory class name. + } else { + statusLogger.warn( + "Usage of the {} property is deprecated. Use the {} property instead.", + LogManager.FACTORY_PROPERTY_NAME, + PROVIDER_PROPERTY_NAME); + for (final Provider provider : providers) { + if (factoryClassName.equals(provider.getClassName())) { + selected = provider; + break; + } + } + } + // 2b. Instantiate + if (selected == null) { + statusLogger.warn( + "No provider found using {} as logger context factory. The factory will be instantiated directly.", + factoryClassName); + try { + final Class clazz = LoaderUtil.loadClass(factoryClassName); + if (LoggerContextFactory.class.isAssignableFrom(clazz)) { + selected = new Provider(null, Strings.EMPTY, clazz.asSubclass(LoggerContextFactory.class)); + } else { + statusLogger.error( + "Class {} specified in the {} system property does not extend {}", + factoryClassName, + LogManager.FACTORY_PROPERTY_NAME, + LoggerContextFactory.class.getName()); + } + } catch (final Exception e) { + statusLogger.error( + "Unable to create class {} specified in the {} system property", + factoryClassName, + LogManager.FACTORY_PROPERTY_NAME, + e); + } + } + } + // 3. Select a provider automatically. + if (selected == null) { + final Comparator comparator = Comparator.comparing(Provider::getPriority); + switch (providers.size()) { + case 0: + statusLogger.error("Log4j API could not find a logging provider."); + break; + case 1: + break; + default: + statusLogger.warn(providers.stream() + .sorted(comparator) + .map(Provider::toString) + .collect(Collectors.joining("\n", "Log4j API found multiple logging providers:\n", ""))); + break; + } + selected = providers.stream().max(comparator).orElseGet(SimpleProvider::new); + } + statusLogger.info("Using provider:\n{}", selected); + return selected; + } + public static ClassLoader findClassLoader() { return LoaderUtil.getThreadContextClassLoader(); } 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..3d69978cdfe 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -25,7 +25,7 @@ * @since 2.7 */ public interface ReadOnlyStringMap extends Serializable { - + /** * Returns a non-{@code null} mutable {@code Map} containing a snapshot of this data structure. * 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..38d6702b339 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.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.util; + +import static java.util.Objects.requireNonNull; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.apache.logging.log4j.Logger; + +/** + * An utility class to retrieve services in a safe way. + *

+ * This class should be considered internal. + *

+ *

+ * A common source of {@link ServiceLoader} failures, when running in a multi-classloader environment, is the + * presence of multiple classes with the same class name in the same classloader hierarchy. Since {@code + * ServiceLoader} retrieves services by class name, it is entirely possible that the registered services don't + * extend the required interface and cause an exception to be thrown by {@code ServiceLoader}. + *

+ *

+ * The purpose of this class is to: + *

+ *
    + *
  1. skip faulty services, allowing for a partial retrieval of the good ones,
  2. + *
  3. allow to integrate other sources of services like OSGi services.
  4. + *
+ * + * @since 2.18.0 + */ +@InternalApi +public final class ServiceLoaderUtil { + + private static final int MAX_BROKEN_SERVICES = 8; + + private ServiceLoaderUtil() {} + + /** + * Retrieves services registered with {@link ServiceLoader} + *

+ * It ignores the most common service loading errors. + *

+ * @param serviceType The service type to use for OSGi service retrieval. + * @param serviceLoader The service loader instance to use. + * @param logger The logger to use to report service failures. + * @return A stream of all correctly loaded services. + * @since 2.24.0 + */ + public static Stream safeStream( + final Class serviceType, final ServiceLoader serviceLoader, final Logger logger) { + requireNonNull(serviceLoader, "serviceLoader"); + final Collection> classes = new HashSet<>(); + final Stream services = + StreamSupport.stream(new ServiceLoaderSpliterator<>(serviceType, serviceLoader, logger), false); + // Caller class may be null + final Class callerClass = StackLocatorUtil.getCallerClass(2); + final Stream allServices = OsgiServiceLocator.isAvailable() && callerClass != null + ? Stream.concat(services, OsgiServiceLocator.loadServices(serviceType, callerClass, logger)) + : services; + return allServices + // only the first occurrence of a class + .filter(service -> classes.add(service.getClass())); + } + + private static final class ServiceLoaderSpliterator extends Spliterators.AbstractSpliterator { + private final String serviceName; + private final Iterator serviceIterator; + private final Logger logger; + + private ServiceLoaderSpliterator( + final Class serviceType, final Iterable serviceLoader, final Logger logger) { + super(Long.MAX_VALUE, ORDERED | NONNULL | IMMUTABLE); + this.serviceName = serviceType.getName(); + this.serviceIterator = serviceLoader.iterator(); + this.logger = logger; + } + + @Override + public boolean tryAdvance(final Consumer action) { + int i = MAX_BROKEN_SERVICES; + while (i-- > 0) { + try { + if (serviceIterator.hasNext()) { + action.accept(serviceIterator.next()); + return true; + } + } catch (final ServiceConfigurationError | LinkageError e) { + logger.warn("Unable to load implementation for service {}", serviceName, e); + } catch (final Exception e) { + logger.warn("Unexpected exception while loading implementation for service {}", serviceName, e); + throw e; + } + } + return false; + } + } +} 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..f74b10fbf2f 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 @@ -1,39 +1,30 @@ /* * 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.logging.log4j.util; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.StreamCorruptedException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.io.Serializable; import java.util.Arrays; -import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Map; import java.util.Objects; - -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.internal.SerializationUtil; /** * Consider this class private. @@ -58,26 +49,25 @@ * * @since 2.7 */ +@InternalApi public class SortedArrayStringMap implements IndexedStringMap { /** * The default initial capacity. */ private static final int DEFAULT_INITIAL_CAPACITY = 4; + 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; @@ -88,41 +78,6 @@ public void accept(final String key, final Object value, final StringMap context */ private transient int size; - private static final Method setObjectInputFilter; - private static final Method getObjectInputFilter; - private static final Method newObjectInputFilter; - - static { - Method[] methods = ObjectInputStream.class.getMethods(); - Method setMethod = null; - Method getMethod = null; - for (Method method : methods) { - if (method.getName().equals("setObjectInputFilter")) { - setMethod = method; - } else if (method.getName().equals("getObjectInputFilter")) { - getMethod = method; - } - } - Method newMethod = null; - try { - if (setMethod != null) { - Class clazz = Class.forName("org.apache.logging.log4j.util.internal.DefaultObjectInputFilter"); - methods = clazz.getMethods(); - for (Method method : methods) { - if (method.getName().equals("newInstance") && Modifier.isStatic(method.getModifiers())) { - newMethod = method; - break; - } - } - } - } catch (ClassNotFoundException ex) { - // Ignore the exception - } - newObjectInputFilter = newMethod; - setObjectInputFilter = setMethod; - getObjectInputFilter = getMethod; - } - /** * The next size value at which to resize (capacity * load factor). * @serial @@ -130,6 +85,7 @@ public void accept(final String key, final Object value, final StringMap context // If table == EMPTY_TABLE then this is the initial capacity at which the // table will be created when inflated. private int threshold; + private boolean immutable; private transient boolean iterating; @@ -153,10 +109,14 @@ public SortedArrayStringMap(final ReadOnlyStringMap other) { } } + /** + * @since 2.8 + */ 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()); } } @@ -448,39 +408,22 @@ public boolean equals(final Object obj) { if (obj == this) { return true; } - if (!(obj instanceof SortedArrayStringMap)) { + if (!(obj instanceof ReadOnlyStringMap)) { return false; } - final SortedArrayStringMap other = (SortedArrayStringMap) obj; - if (this.size() != other.size()) { + if (size() != ((ReadOnlyStringMap) obj).size()) { return false; } - for (int i = 0; i < size(); i++) { - if (!Objects.equals(keys[i], other.keys[i])) { - return false; - } - if (!Objects.equals(values[i], other.values[i])) { - return false; - } - } - return true; + + // Convert to maps and compare + final Map thisMap = toMap(); + final Map otherMap = ((ReadOnlyStringMap) obj).toMap(); + return thisMap.equals(otherMap); } @Override public int hashCode() { - int result = 37; - result = HASHVAL * result + size; - result = HASHVAL * result + hashCode(keys, size); - result = HASHVAL * result + hashCode(values, size); - return result; - } - - private static int hashCode(final Object[] values, final int length) { - int result = 1; - for (int i = 0; i < length; i++) { - result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode()); - } - return result; + return toMap().hashCode(); } @Override @@ -527,51 +470,10 @@ private void writeObject(final java.io.ObjectOutputStream s) throws IOException if (size > 0) { for (int i = 0; i < size; i++) { s.writeObject(keys[i]); - try { - s.writeObject(marshall(values[i])); - } catch (final Exception e) { - handleSerializationException(e, i, keys[i]); - s.writeObject(null); - } - } - } - } - - private static byte[] marshall(final Object obj) throws IOException { - if (obj == null) { - return null; - } - final ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try (ObjectOutputStream oos = new ObjectOutputStream(bout)) { - oos.writeObject(obj); - oos.flush(); - return bout.toByteArray(); - } - } - - private static Object unmarshall(final byte[] data, ObjectInputStream inputStream) - throws IOException, ClassNotFoundException { - final ByteArrayInputStream bin = new ByteArrayInputStream(data); - Collection allowedClasses = null; - 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); - ois = new ObjectInputStream(bin); - setObjectInputFilter.invoke(ois, filter); - } catch (IllegalAccessException | InvocationTargetException ex) { - throw new StreamCorruptedException("Unable to set ObjectInputFilter on stream"); + SerializationUtil.writeWrappedObject( + (values[i] instanceof Serializable) ? (Serializable) values[i] : null, s); } } - try { - return ois.readObject(); - } finally { - ois.close(); - } } /** @@ -591,10 +493,8 @@ private static int ceilingNextPowerOfTwo(final int x) { * Reconstitute the {@code SortedArrayStringMap} instance from a stream (i.e., * deserialize it). */ - private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { - if (!(s instanceof FilteredObjectInputStream) && setObjectInputFilter == null) { - throw new IllegalArgumentException("readObject requires a FilteredObjectInputStream or an ObjectInputStream that accepts an ObjectInputFilter"); - } + private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + SerializationUtil.assertFiltered(s); // Read in the threshold (ignored), and any hidden stuff s.defaultReadObject(); @@ -624,18 +524,8 @@ private void readObject(final java.io.ObjectInputStream s) throws IOException, // Read the keys and values, and put the mappings in the arrays for (int i = 0; i < mappings; i++) { keys[i] = (String) s.readObject(); - try { - final byte[] marshalledObject = (byte[]) s.readObject(); - values[i] = marshalledObject == null ? null : unmarshall(marshalledObject, s); - } catch (final Exception | LinkageError error) { - handleSerializationException(error, i, keys[i]); - values[i] = null; - } + values[i] = SerializationUtil.readWrappedObject(s); } size = mappings; } - - private void handleSerializationException(final Throwable t, final int i, final String key) { - StatusLogger.getLogger().warn("Ignoring {} for key[{}] ('{}')", String.valueOf(t), i, keys[i]); - } } 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..13cf6b39eb9 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 @@ -1,26 +1,32 @@ /* * 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.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; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** - * Consider this class private. Provides various methods to determine the caller class.

Background

+ * 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, @@ -43,75 +49,127 @@ * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to * examination of every virtual frame of execution. *

+ * + * @since 2.9.0 */ +@InternalApi public final class StackLocator { - private static PrivateSecurityManager SECURITY_MANAGER; + private static final Logger LOGGER = StatusLogger.getLogger(); - // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle... - // CHECKSTYLE:OFF - static final int JDK_7u25_OFFSET; - // CHECKSTYLE:OFF + /** TODO Consider removing now that we require Java 8. */ + static final int JDK_7U25_OFFSET; - private static final Method GET_CALLER_CLASS; + private static final Method GET_CALLER_CLASS_METHOD; private static final StackLocator INSTANCE; + /** TODO: Use Object.class. */ + private static final Class DEFAULT_CALLER_CLASS = null; + static { - Method getCallerClass; + Method getCallerClassMethod; 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); + // Do not use `LoaderUtil` here, since it causes a cycle of dependencies: + // LoaderUtil -> PropertiesUtil -> ServiceLoaderUtil -> StackLocator + final Class sunReflectionClass = + Class.forName("sun.reflect.Reflection", true, ClassLoader.getSystemClassLoader()); + getCallerClassMethod = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class); + Object o = getCallerClassMethod.invoke(null, 0); + getCallerClassMethod.invoke(null, 0); if (o == null || o != sunReflectionClass) { - getCallerClass = null; + getCallerClassMethod = null; java7u25CompensationOffset = -1; } else { - o = getCallerClass.invoke(null, 1); + o = getCallerClassMethod.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."); + LOGGER.warn( + "Unexpected result from `sun.reflect.Reflection.getCallerClass(int)`, adjusting offset for future calls."); java7u25CompensationOffset = 1; } } } catch (final Exception | LinkageError e) { - System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance."); - getCallerClass = null; + // We can not use `Constants` here, since they depend on `PropertiesUtil`. + LOGGER.warn( + System.getProperty("java.version", "").startsWith("1.8") + ? "`sun.reflect.Reflection.getCallerClass(int)` is not supported. This will impact location-based features." + : "Runtime environment or build system does not support multi-release JARs. This will impact location-based features."); + getCallerClassMethod = null; java7u25CompensationOffset = -1; } - GET_CALLER_CLASS = getCallerClass; - JDK_7u25_OFFSET = java7u25CompensationOffset; - + GET_CALLER_CLASS_METHOD = getCallerClassMethod; + JDK_7U25_OFFSET = java7u25CompensationOffset; INSTANCE = new StackLocator(); } + /** + * Gets the singleton instance. + * + * @return the singleton instance. + */ public static StackLocator getInstance() { return INSTANCE; } - private StackLocator() { - } + 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"); + } + boolean foundSentinel = false; + Class clazz; + for (int i = 2; null != (clazz = getCallerClass(i)); i++) { + if (sentinelClass.equals(clazz)) { + foundSentinel = true; + } else if (foundSentinel && callerPredicate.test(clazz)) { + return clazz; + } + } + return DEFAULT_CALLER_CLASS; + } + + /** + * Gets the Class of the method 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)}.
  • + *
+ * + * @param depth The stack frame count to walk. + * @return A class or null. + * @throws IndexOutOfBoundsException if depth is negative. + */ // migrated from ReflectiveCallerClassUtility @PerformanceSensitive public Class getCallerClass(final int depth) { if (depth < 0) { throw new IndexOutOfBoundsException(Integer.toString(depth)); } + if (GET_CALLER_CLASS_METHOD == null) { + return DEFAULT_CALLER_CLASS; + } // 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); + return (Class) GET_CALLER_CLASS_METHOD.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; + return DEFAULT_CALLER_CLASS; } } @@ -130,7 +188,7 @@ public Class getCallerClass(final String fqcn, final String pkg) { } } // TODO: return Object.class - return null; + return DEFAULT_CALLER_CLASS; } // added for use in LoggerAdapter implementations mainly @@ -150,24 +208,21 @@ public Class getCallerClass(final Class anchor) { return Object.class; } + /** + * @since 2.17.2 + */ // migrated from ThrowableProxy @PerformanceSensitive - public Stack> getCurrentStackTrace() { + public Deque> 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; + if (PrivateSecurityManagerStackTraceUtil.isEnabled()) { + return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace(); } // slower version using getCallerClass where we cannot use a SecurityManager - final Stack> classes = new Stack<>(); + final Deque> classes = new ArrayDeque<>(); Class clazz; for (int i = 1; null != (clazz = getCallerClass(i)); i++) { - classes.push(clazz); + classes.addLast(clazz); } return classes; } @@ -178,13 +233,16 @@ public StackTraceElement calcLocation(final String fqcnOfLogger) { } // 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--) { + boolean found = false; + for (int i = 0; i < stackTrace.length; i++) { final String className = stackTrace[i].getClassName(); if (fqcnOfLogger.equals(className)) { - return last; + found = true; + continue; + } + if (found && !fqcnOfLogger.equals(className)) { + return stackTrace[i]; } - last = stackTrace[i]; } return null; } @@ -192,9 +250,8 @@ public StackTraceElement calcLocation(final String fqcnOfLogger) { 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) { + for (final StackTraceElement element : new Throwable().getStackTrace()) { if (isValid(element)) { if (i == depth) { return element; @@ -238,17 +295,4 @@ private boolean isValid(final StackTraceElement element) { // any others? return true; } - - protected PrivateSecurityManager getSecurityManager() { - return SECURITY_MANAGER; - } - - 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/StackLocatorUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java index 56812fe4469..89e35aa616f 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 @@ -1,35 +1,41 @@ /* * 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.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

+ * Consider this class private. Provides various methods to determine the caller class. + * + * @since 2.9.0 */ +@InternalApi public final class StackLocatorUtil { private static StackLocator stackLocator = null; + private static volatile boolean errorLogged; static { stackLocator = StackLocator.getInstance(); } - private StackLocatorUtil() { - } + private StackLocatorUtil() {} // 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 @@ -43,31 +49,88 @@ 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. + * @since 2.17.2 + */ + @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. + * @since 2.15.0 + */ + @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) { return stackLocator.getCallerClass(anchor); } + /** + * @since 2.17.2 + */ // 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 (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/StringBuilderFormattable.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilderFormattable.java index cb15f08bc04..fa8d5c28893 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilderFormattable.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilderFormattable.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; 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..8f697d53f1f 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 @@ -1,32 +1,54 @@ /* * 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.logging.log4j.util; -import java.util.Map.Entry; - import static java.lang.Character.toLowerCase; +import java.util.Map.Entry; + /** * Consider this class private. + * @since 2.3 */ +@InternalApi public final class StringBuilders { - private StringBuilders() { + + private static final Class timeClass; + private static final Class dateClass; + + static { + Class clazz; + try { + clazz = Class.forName("java.sql.Time"); + } catch (ClassNotFoundException ex) { + clazz = null; + } + timeClass = clazz; + + try { + clazz = Class.forName("java.sql.Date"); + } catch (ClassNotFoundException ex) { + clazz = null; + } + dateClass = clazz; } + private StringBuilders() {} + /** * Appends in the following format: double quoted value. * @@ -58,7 +80,11 @@ 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); + return sb.append(key) + .append(Chars.EQ) + .append(Chars.DQUOTE) + .append(value) + .append(Chars.DQUOTE); } /** @@ -67,6 +93,7 @@ public static StringBuilder appendKeyDqValue(final StringBuilder sb, final Strin * * @param stringBuilder the StringBuilder to append the value to * @param obj the object whose text representation to append to the StringBuilder + * @since 2.7 */ public static void appendValue(final StringBuilder stringBuilder, final Object obj) { if (!appendSpecificTypes(stringBuilder, obj)) { @@ -74,6 +101,9 @@ public static void appendValue(final StringBuilder stringBuilder, final Object o } } + /** + * @since 2.10.0 + */ public static boolean appendSpecificTypes(final StringBuilder stringBuilder, final Object obj) { if (obj == null || obj instanceof String) { stringBuilder.append((String) obj); @@ -97,12 +127,28 @@ 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) || isDate(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 timeClass != null && timeClass.isAssignableFrom(obj.getClass()); + } + + /* + Check to see if obj is an instance of java.sql.Date without requiring the java.sql module. + */ + private static boolean isDate(final Object obj) { + return dateClass != null && dateClass.isAssignableFrom(obj.getClass()); + } + /** * Returns true if the specified section of the left CharSequence equals the specified section of the right * CharSequence. @@ -114,9 +160,15 @@ public static boolean appendSpecificTypes(final StringBuilder stringBuilder, fin * @param rightOffset start index in the right CharSequence * @param rightLength length of the section in the right CharSequence * @return true if equal, false otherwise + * @since 2.8 */ - public static boolean equals(final CharSequence left, final int leftOffset, final int leftLength, - final CharSequence right, final int rightOffset, final int rightLength) { + public static boolean equals( + final CharSequence left, + final int leftOffset, + final int leftLength, + final CharSequence right, + final int rightOffset, + final int rightLength) { if (leftLength == rightLength) { for (int i = 0; i < rightLength; i++) { if (left.charAt(i + leftOffset) != right.charAt(i + rightOffset)) { @@ -139,9 +191,15 @@ public static boolean equals(final CharSequence left, final int leftOffset, fina * @param rightOffset start index in the right CharSequence * @param rightLength length of the section in the right CharSequence * @return true if equal ignoring case, false otherwise + * @since 2.8 */ - public static boolean equalsIgnoreCase(final CharSequence left, final int leftOffset, final int leftLength, - final CharSequence right, final int rightOffset, final int rightLength) { + public static boolean equalsIgnoreCase( + final CharSequence left, + final int leftOffset, + final int leftLength, + final CharSequence right, + final int rightOffset, + final int rightLength) { if (leftLength == rightLength) { for (int i = 0; i < rightLength; i++) { if (toLowerCase(left.charAt(i + leftOffset)) != toLowerCase(right.charAt(i + rightOffset))) { @@ -159,7 +217,7 @@ public static boolean equalsIgnoreCase(final CharSequence left, final int leftOf * * @param stringBuilder the StringBuilder to check * @param maxSize the maximum number of characters the StringBuilder is allowed to have - * @since 2.9 + * @since 2.9.0 */ public static void trimToMaxSize(final StringBuilder stringBuilder, final int maxSize) { if (stringBuilder != null && stringBuilder.capacity() > maxSize) { @@ -168,78 +226,150 @@ public static void trimToMaxSize(final StringBuilder stringBuilder, final int ma } } + /** + * @since 2.10.0 + */ 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; + } + + /** + * @since 2.10.0 + */ 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 '&': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "amp;"); + escapeCount += 4; break; case '<': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "lt;"); + 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(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 'p'); + toAppendTo.setCharAt(lastPos--, 'm'); + toAppendTo.setCharAt(lastPos--, 'a'); + toAppendTo.setCharAt(lastPos--, '&'); + break; + case '<': + 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..6d4ef39f931 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -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..b33de7dd025 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 @@ -1,67 +1,91 @@ /* * 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.logging.log4j.util; +import static org.apache.logging.log4j.util.StringBuilders.trimToMaxSize; + import java.util.Iterator; import java.util.Locale; import java.util.Objects; /** * Consider this class private. - * - * @see Apache Commons Lang + * + * @see Apache Commons Lang */ +@InternalApi public final class Strings { + // 518 allows the `StringBuilder` to resize three times from its initial size. + // This should be sufficient for most use cases. + private static final int MAX_FORMAT_BUFFER_LENGTH = 518; + + private static final ThreadLocal FORMAT_BUFFER_REF = ThreadLocal.withInitial(StringBuilder::new); + /** * The empty string. */ public static final String EMPTY = ""; - + + private static final String COMMA_DELIMITED_RE = "\\s*,\\s*"; + + /** + * The empty array. + * @since 2.15.0 + */ + public static final String[] EMPTY_ARRAY = {}; + /** * OS-dependent line separator, defaults to {@code "\n"} if the system property {@code ""line.separator"} cannot be * read. + * @since 2.7 */ - public static final String LINE_SEPARATOR = PropertiesUtil.getProperties().getStringProperty("line.separator", - "\n"); - - private Strings() { - // empty - } + public static final String LINE_SEPARATOR = System.lineSeparator(); /** * Returns a double quoted string. - * + * * @param str a String * @return {@code "str"} + * @since 2.3 */ public static String dquote(final String str) { return Chars.DQUOTE + str + Chars.DQUOTE; } /** - * 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)} + * @since 2.0.1 */ 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; } /** @@ -98,6 +122,7 @@ public static boolean isEmpty(final CharSequence cs) { * * @param s the String to check, may be {@code null} * @return {@code true} if the String is non-{@code null} and has content after being trimmed. + * @since 2.0.1 */ public static boolean isNotBlank(final String s) { return !isBlank(s); @@ -127,55 +152,6 @@ public static boolean isNotEmpty(final CharSequence cs) { return !isEmpty(cs); } - /** - * Returns a quoted string. - * - * @param str a String - * @return {@code 'str'} - */ - public static String quote(final String str) { - return Chars.QUOTE + str + Chars.QUOTE; - } - - /** - * Shorthand for {@code str.toUpperCase(Locale.ROOT);} - * @param str The string to upper case. - * @return a new string - * @see String#toLowerCase(Locale) - */ - public 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 - * empty ("") after the trim or if it is {@code null}. - * - *

- * The String is trimmed using {@link String#trim()}. Trim removes start and end characters <= 32. - *

- * - *
-     * Strings.trimToNull(null)          = null
-     * Strings.trimToNull("")            = null
-     * Strings.trimToNull("     ")       = null
-     * Strings.trimToNull("abc")         = "abc"
-     * Strings.trimToNull("    abc    ") = "abc"
-     * 
- * - *

- * Copied from Apache Commons Lang org.apache.commons.lang3.StringUtils.trimToNull(String) - *

- * - * @param str the String to be trimmed, may be null - * @return the trimmed String, {@code null} if only chars <= 32, empty or null String input - */ - public static String trimToNull(final String str) { - final String ts = str == null ? null : str.trim(); - return isEmpty(ts) ? null : ts; - } - /** *

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

@@ -186,6 +162,7 @@ public static String trimToNull(final String str) { * @param iterable the {@code Iterable} providing the values to join together, may be null * @param separator the separator character to use * @return the joined String, {@code null} if null iterator input + * @since 2.7 */ public static String join(final Iterable iterable, final char separator) { if (iterable == null) { @@ -204,6 +181,7 @@ public static String join(final Iterable iterable, final char separator) { * @param iterator the {@code Iterator} of values to join together, may be null * @param separator the separator character to use * @return the joined String, {@code null} if null iterator input + * @since 2.7 */ public static String join(final Iterator iterator, final char separator) { @@ -216,7 +194,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 +214,161 @@ public static String join(final Iterator iterator, final char separator) { return buf.toString(); } + /** + * @since 2.17.2 + */ + public static String[] splitList(final String string) { + return string != null ? string.split(COMMA_DELIMITED_RE) : new String[0]; + } + + /** + *

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 + * @since 2.11.2 + */ + 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'} + * @since 2.3 + */ + public static String quote(final String str) { + return Chars.QUOTE + str + Chars.QUOTE; + } + + /** + *

+ * Removes control characters (char <= 32) from both ends of this String returning {@code null} if the String is + * empty ("") after the trim or if it is {@code null}. + * + *

+ * The String is trimmed using {@link String#trim()}. Trim removes start and end characters <= 32. + *

+ * + *
+     * Strings.trimToNull(null)          = null
+     * Strings.trimToNull("")            = null
+     * Strings.trimToNull("     ")       = null
+     * Strings.trimToNull("abc")         = "abc"
+     * Strings.trimToNull("    abc    ") = "abc"
+     * 
+ * + *

+ * Copied from Apache Commons Lang org.apache.commons.lang3.StringUtils.trimToNull(String) + *

+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, {@code null} if only chars <= 32, empty or null String input + */ + public static String trimToNull(final String str) { + final String ts = str == null ? null : str.trim(); + return isEmpty(ts) ? null : ts; + } + + private Strings() { + // empty + } + + /** + * Shorthand for {@code str.toLowerCase(Locale.ROOT);} + * @param str The string to upper case. + * @return a new string + * @see String#toLowerCase(Locale) + * @since 2.17.2 + */ + public static String toRootLowerCase(final String str) { + return str.toLowerCase(Locale.ROOT); + } + + /** + * Shorthand for {@code str.toUpperCase(Locale.ROOT);} + * @param str The string to lower case. + * @return a new string + * @see String#toLowerCase(Locale) + * @since 2.6 + */ + public static String toRootUpperCase(final String str) { + return str.toUpperCase(Locale.ROOT); + } + + /** + * Concatenates 2 Strings without allocation. + * @param str1 the first string. + * @param str2 the second string. + * @return the concatenated String. + * @since 2.15.0 + */ + public static String concat(final String str1, final String str2) { + if (isEmpty(str1)) { + return str2; + } else if (isEmpty(str2)) { + return str1; + } + final StringBuilder sb = FORMAT_BUFFER_REF.get(); + try { + return sb.append(str1).append(str2).toString(); + } finally { + trimToMaxSize(sb, MAX_FORMAT_BUFFER_LENGTH); + 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 + * @since 2.14.0 + */ + public static String repeat(final String str, final int count) { + Objects.requireNonNull(str, "str"); + if (count < 0) { + throw new IllegalArgumentException("count"); + } + final StringBuilder sb = FORMAT_BUFFER_REF.get(); + try { + for (int index = 0; index < count; index++) { + sb.append(str); + } + return sb.toString(); + } finally { + trimToMaxSize(sb, MAX_FORMAT_BUFFER_LENGTH); + 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..45ad5c667d2 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.util; /** @@ -32,12 +31,15 @@ * * @since 2.4 */ -public interface Supplier { +@FunctionalInterface +@InternalApi +public interface Supplier extends java.util.function.Supplier { /** * Gets a value. * * @return a value */ + @Override T get(); } 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..3471f1db8e4 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 @@ -1,42 +1,95 @@ /* * 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.logging.log4j.util; -import java.util.Map; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import java.util.Collection; +import java.util.Objects; +import java.util.Properties; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; /** - * 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 */ +@ServiceProvider(value = PropertySource.class, resolution = Resolution.OPTIONAL) public class SystemPropertiesPropertySource implements PropertySource { + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final int DEFAULT_PRIORITY = 0; private static final String PREFIX = "log4j2."; + private static final PropertySource INSTANCE = new SystemPropertiesPropertySource(); + + /** + * Method used by Java 9+ to instantiate providers + * @since 2.24.0 + * @see java.util.ServiceLoader + */ + public static PropertySource provider() { + return INSTANCE; + } + + /** + * @since 2.20.0 + */ + public static String getSystemProperty(final String key, final String defaultValue) { + final String value = INSTANCE.getProperty(key); + return value != null ? value : defaultValue; + } + + private static void logException(final SecurityException error) { + LOGGER.error("The Java system properties are not available to Log4j due to security restrictions.", error); + } + + private static void logException(final SecurityException error, final String key) { + LOGGER.error("The Java system property {} is not available to Log4j due to security restrictions.", key, error); + } + @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) { + logException(e); + 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 +98,28 @@ public CharSequence getNormalForm(final Iterable tokens) return PREFIX + Util.joinAsCamelCase(tokens); } + @Override + public Collection getPropertyNames() { + try { + return System.getProperties().stringPropertyNames(); + } catch (final SecurityException e) { + logException(e); + } + return PropertySource.super.getPropertyNames(); + } + + @Override + public String getProperty(final String key) { + try { + return System.getProperty(key); + } catch (final SecurityException e) { + logException(e, key); + } + return PropertySource.super.getProperty(key); + } + + @Override + public boolean containsProperty(final String key) { + return getProperty(key) != null; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java new file mode 100644 index 00000000000..d4d614ad4d0 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Serializable; +import java.text.DecimalFormat; + +/** + * 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. + * + * @since 2.12.0 + */ +public class Timer implements Serializable, StringBuilderFormattable { + private static final long serialVersionUID = 9175191792439630013L; + + private final String name; // The timer's name + + /** + * @since 2.12.0 + */ + 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 ThreadLocal startTime = new ThreadLocal() { + @Override + protected Long initialValue() { + return 0L; + } + }; + + /** + * Constructor. + * @param name the timer name. + */ + public Timer(final String name) { + this(name, 0); + } + + /** + * Constructor. + * + * @param name the timer name. + */ + public Timer(final String name, final int iterations) { + this.name = name; + status = Status.Stopped; + this.iterations = (iterations > 0) ? iterations : 0; + } + + /** + * Start the timer. + */ + public synchronized void start() { + startTime.set(System.nanoTime()); + elapsedTime = 0; + status = Status.Started; + } + + public synchronized void startOrResume() { + if (status == Status.Stopped) { + start(); + } else { + resume(); + } + } + + /** + * Stop the timer. + */ + public synchronized String stop() { + elapsedTime += System.nanoTime() - startTime.get(); + startTime.set(0L); + status = Status.Stopped; + return toString(); + } + + /** + * Pause the timer. + */ + public synchronized void pause() { + elapsedTime += System.nanoTime() - startTime.get(); + startTime.set(0L); + status = Status.Paused; + } + + /** + * Resume the timer. + */ + public synchronized void resume() { + startTime.set(System.nanoTime()); + status = Status.Started; + } + + /** + * Accessor for the name. + * @return the timer's name. + */ + public String getName() { + return name; + } + + /** + * Access the elapsed time. + * + * @return the elapsed time. + */ + public long getElapsedTime() { + return elapsedTime / 1000000; + } + + /** + * Access the elapsed time. + * + * @return the elapsed time. + */ + public long getElapsedNanoTime() { + return elapsedTime; + } + + /** + * Returns the name of the last operation performed on this timer (Start, Stop, Pause or + * Resume). + * @return the string representing the last operation performed. + */ + public Status getStatus() { + return status; + } + + /** + * Returns the String representation of the timer based upon its current state + */ + @Override + public String toString() { + final StringBuilder result = new StringBuilder(); + formatTo(result); + return result.toString(); + } + + @Override + public void formatTo(final StringBuilder buffer) { + buffer.append("Timer ").append(name); + switch (status) { + case Started: + buffer.append(" started"); + break; + case Paused: + buffer.append(" paused"); + break; + case Stopped: + long nanoseconds = elapsedTime; + // Get elapsed hours + long hours = nanoseconds / NANO_PER_HOUR; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_HOUR; + // Get minutes + long minutes = nanoseconds / NANO_PER_MINUTE; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_MINUTE; + // Get seconds + long seconds = nanoseconds / NANO_PER_SECOND; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_SECOND; + + String elapsed = Strings.EMPTY; + + if (hours > 0) { + elapsed += hours + " hours "; + } + if (minutes > 0 || hours > 0) { + elapsed += minutes + " minutes "; + } + + DecimalFormat numFormat; + numFormat = new DecimalFormat("#0"); + elapsed += numFormat.format(seconds) + '.'; + numFormat = new DecimalFormat("000000000"); + elapsed += numFormat.format(nanoseconds) + " seconds"; + buffer.append(" stopped. Elapsed time: ").append(elapsed); + if (iterations > 0) { + nanoseconds = elapsedTime / iterations; + // Get elapsed hours + hours = nanoseconds / NANO_PER_HOUR; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_HOUR; + // Get minutes + minutes = nanoseconds / NANO_PER_MINUTE; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_MINUTE; + // Get seconds + seconds = nanoseconds / NANO_PER_SECOND; + // Get remaining nanoseconds + nanoseconds = nanoseconds % NANO_PER_SECOND; + + elapsed = Strings.EMPTY; + + if (hours > 0) { + elapsed += hours + " hours "; + } + if (minutes > 0 || hours > 0) { + elapsed += minutes + " minutes "; + } + + numFormat = new DecimalFormat("#0"); + elapsed += numFormat.format(seconds) + '.'; + numFormat = new DecimalFormat("000000000"); + elapsed += numFormat.format(nanoseconds) + " seconds"; + buffer.append(" Average per iteration: ").append(elapsed); + } + break; + default: + buffer.append(' ').append(status); + break; + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Timer)) { + return false; + } + + final Timer timer = (Timer) o; + + if (elapsedTime != timer.elapsedTime) { + return false; + } + if (startTime != timer.startTime) { + return false; + } + 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; + } + + @Override + public int hashCode() { + int result; + result = (name != null ? name.hashCode() : 0); + result = 29 * result + (status != null ? status.hashCode() : 0); + 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/TriConsumer.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java index e9ba09eda45..f307dada6e7 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; 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..fd577eaa45c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.util; @@ -62,6 +62,11 @@ public class Unbox { * Such memory leaks will not occur if only JDK classes are stored in ThreadLocals. *

*/ + /* + * https://errorprone.info/bugpattern/ThreadLocalUsage + * Instance thread locals are not a problem here, since this class is a singleton. + */ + @SuppressWarnings("ThreadLocalUsage") private static class WebSafeState { private final ThreadLocal ringBuffer = new ThreadLocal<>(); private final ThreadLocal current = new ThreadLocal<>(); @@ -81,24 +86,12 @@ public StringBuilder getStringBuilder() { result.setLength(0); return result; } - - public boolean isBoxedPrimitive(final StringBuilder text) { - final StringBuilder[] array = ringBuffer.get(); - if (array == null) { - return false; - } - for (int i = 0; i < array.length; i++) { - if (text == array[i]) { - return true; - } - } - return false; - } } private static class State { private final StringBuilder[] ringBuffer = new StringBuilder[RINGBUFFER_SIZE]; private int current; + State() { for (int i = 0; i < ringBuffer.length; i++) { ringBuffer[i] = new StringBuilder(21); @@ -110,16 +103,8 @@ public StringBuilder getStringBuilder() { result.setLength(0); return result; } - - public boolean isBoxedPrimitive(final StringBuilder text) { - for (int i = 0; i < ringBuffer.length; i++) { - if (text == ringBuffer[i]) { - return true; - } - } - return false; - } } + private static ThreadLocal threadLocalState = new ThreadLocal<>(); private static WebSafeState webSafeState = new WebSafeState(); @@ -128,19 +113,22 @@ private Unbox() { } private static int calculateRingBufferSize(final String propertyName) { - final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName, - String.valueOf(RINGBUFFER_MIN_SIZE)); + final String userPreferredRBSize = + PropertiesUtil.getProperties().getStringProperty(propertyName, String.valueOf(RINGBUFFER_MIN_SIZE)); try { - int size = Integer.parseInt(userPreferredRBSize); + int size = Integer.parseInt(userPreferredRBSize.trim()); if (size < RINGBUFFER_MIN_SIZE) { size = RINGBUFFER_MIN_SIZE; - LOGGER.warn("Invalid {} {}, using minimum size {}.", propertyName, userPreferredRBSize, + LOGGER.warn( + "Invalid {} {}, using minimum size {}.", + propertyName, + userPreferredRBSize, RINGBUFFER_MIN_SIZE); } return ceilingNextPowerOfTwo(size); } catch (final Exception ex) { - LOGGER.warn("Invalid {} {}, using default size {}.", propertyName, userPreferredRBSize, - RINGBUFFER_MIN_SIZE); + LOGGER.warn( + "Invalid {} {}, using default size {}.", propertyName, userPreferredRBSize, RINGBUFFER_MIN_SIZE); return RINGBUFFER_MIN_SIZE; } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.java new file mode 100644 index 00000000000..6180f47421f --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/SerializationUtil.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.util.internal; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.io.StreamCorruptedException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.FilteredObjectInputStream; + +/** + * Provides methods to increase the safety of object serialization/deserialization. + */ +public final class SerializationUtil { + + private static final String DEFAULT_FILTER_CLASS = + "org.apache.logging.log4j.util.internal.DefaultObjectInputFilter"; + private static final Method setObjectInputFilter; + private static final Method getObjectInputFilter; + private static final Method newObjectInputFilter; + + static { + Method[] methods = ObjectInputStream.class.getMethods(); + Method setMethod = null; + Method getMethod = null; + for (final Method method : methods) { + if (method.getName().equals("setObjectInputFilter")) { + setMethod = method; + } else if (method.getName().equals("getObjectInputFilter")) { + getMethod = method; + } + } + Method newMethod = null; + try { + if (setMethod != null) { + final Class clazz = Class.forName(DEFAULT_FILTER_CLASS); + methods = clazz.getMethods(); + for (final Method method : methods) { + if (method.getName().equals("newInstance") && Modifier.isStatic(method.getModifiers())) { + newMethod = method; + break; + } + } + } + } catch (final ClassNotFoundException ex) { + // Ignore the exception + } + newObjectInputFilter = newMethod; + setObjectInputFilter = setMethod; + getObjectInputFilter = getMethod; + } + + public static final List REQUIRED_JAVA_CLASSES = Arrays.asList( + "java.math.BigDecimal", + "java.math.BigInteger", + // for Message delegate + "java.rmi.MarshalledObject", + // all primitives + "boolean", + "byte", + "char", + "double", + "float", + "int", + "long", + "short"); + + public static final List REQUIRED_JAVA_PACKAGES = + Arrays.asList("java.lang.", "java.time.", "java.util.", "org.apache.logging.log4j."); + + public static void writeWrappedObject(final Serializable obj, final ObjectOutputStream out) throws IOException { + final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try (final ObjectOutputStream oos = new ObjectOutputStream(bout)) { + oos.writeObject(obj); + oos.flush(); + out.writeObject(bout.toByteArray()); + } + } + + @SuppressFBWarnings( + value = "OBJECT_DESERIALIZATION", + justification = + "Object deserialization uses either Java 9 native filter or our custom filter to limit the kinds of classes deserialized.") + public static Object readWrappedObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + assertFiltered(in); + final byte[] data = (byte[]) in.readObject(); + final ByteArrayInputStream bin = new ByteArrayInputStream(data); + final ObjectInputStream ois; + if (in instanceof FilteredObjectInputStream) { + ois = new FilteredObjectInputStream(bin, ((FilteredObjectInputStream) in).getAllowedClasses()); + } else { + try { + final Object obj = getObjectInputFilter.invoke(in); + final Object filter = newObjectInputFilter.invoke(null, obj); + ois = new ObjectInputStream(bin); + setObjectInputFilter.invoke(ois, filter); + } catch (IllegalAccessException | InvocationTargetException ex) { + throw new StreamCorruptedException("Unable to set ObjectInputFilter on stream"); + } + } + try { + return ois.readObject(); + } catch (final Exception | LinkageError e) { + StatusLogger.getLogger().warn("Ignoring {} during deserialization", e.getMessage()); + return null; + } finally { + ois.close(); + } + } + + public static void assertFiltered(final java.io.ObjectInputStream stream) { + if (!(stream instanceof FilteredObjectInputStream) && setObjectInputFilter == null) { + throw new IllegalArgumentException( + "readObject requires a FilteredObjectInputStream or an ObjectInputStream that accepts an ObjectInputFilter"); + } + } + + /** + * Gets the class name of an array component recursively. + *

+ * If {@code clazz} is not an array class its name is returned. + *

+ * @param clazz the binary name of a class. + */ + public static String stripArray(final Class clazz) { + Class currentClazz = clazz; + while (currentClazz.isArray()) { + currentClazz = currentClazz.getComponentType(); + } + return currentClazz.getName(); + } + + /** + * Gets the class name of an array component recursively. + *

+ * If {@code name} is not the name of an array class it is returned unchanged. + *

+ * @param name the name of a class. + * @see Class#getName() + */ + public static String stripArray(final String name) { + final int offset = name.lastIndexOf('[') + 1; + if (offset == 0) { + return name; + } + // Reference types + if (name.charAt(offset) == 'L') { + return name.substring(offset + 1, name.length() - 1); + } + // Primitive classes + switch (name.substring(offset)) { + case "Z": + return "boolean"; + case "B": + return "byte"; + case "C": + return "char"; + case "D": + return "double"; + case "F": + return "float"; + case "I": + return "int"; + case "J": + return "long"; + case "S": + return "short"; + default: + throw new IllegalArgumentException("Unsupported array class signature '" + name + "'"); + } + } + + private SerializationUtil() {} +} 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..23fcfbd931d 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 @@ -19,4 +19,9 @@ * 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. */ +@Export +@Version("2.25.0") package org.apache.logging.log4j.util; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api/src/main/resources/Log4j-charsets.properties b/log4j-api/src/main/resources/Log4j-charsets.properties index b7e4c8e87d2..d5d615a1f1f 100644 --- a/log4j-api/src/main/resources/Log4j-charsets.properties +++ b/log4j-api/src/main/resources/Log4j-charsets.properties @@ -1,17 +1,19 @@ +# # 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. +# # Mapping based on https://msdn.microsoft.com/en-us/en-en/library/windows/desktop/dd317756(v=vs.85).aspx # Reference for supported Java encodings: https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html diff --git a/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json b/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json new file mode 100644 index 00000000000..9b4eb75dc63 --- /dev/null +++ b/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "log4j2\\.(component|simplelog|StatusLogger)\\.properties" + } + ] + } +} diff --git a/log4j-api/src/site/markdown/index.md b/log4j-api/src/site/markdown/index.md deleted file mode 100644 index f83cc0d7a61..00000000000 --- a/log4j-api/src/site/markdown/index.md +++ /dev/null @@ -1,26 +0,0 @@ - - - -# 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/src/site/site.xml b/log4j-api/src/site/site.xml deleted file mode 100644 index e0a45af2225..00000000000 --- a/log4j-api/src/site/site.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/EventLoggerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/EventLoggerTest.java deleted file mode 100644 index 181a34481ed..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/EventLoggerTest.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; - -import java.util.List; -import java.util.Locale; - -import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Before; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.*; - -import static org.junit.Assert.*; - -/** - * - */ -public class EventLoggerTest { - - TestLogger logger = (TestLogger) LogManager.getLogger("EventLogger"); - List results = logger.getEntries(); - - @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("Transfer@18060", "Transfer Complete", "Audit"); - msg.put("ToAccount", "123456"); - msg.put("FromAccount", "123457"); - msg.put("Amount", "200.00"); - EventLogger.logEvent(msg); - ThreadContext.clearMap(); - assertEquals(1, results.size()); - 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)); - } - -} 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/TestLogger.java b/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java deleted file mode 100644 index 5f7abb7dbda..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java +++ /dev/null @@ -1,190 +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.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.spi.AbstractLogger; - -/** - * - */ -public class TestLogger extends AbstractLogger { - - private static final long serialVersionUID = 1L; - - public TestLogger() { - super(); - } - - public TestLogger(final String name, final MessageFactory messageFactory) { - super(name, messageFactory); - } - - public TestLogger(final String name) { - super(name); - } - - private final List list = new ArrayList<>(); - - public List getEntries() { - return list; - } - - @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { - final StringBuilder sb = new StringBuilder(); - if (marker != null) { - sb.append(marker); - } - sb.append(' '); - sb.append(level.toString()); - sb.append(' '); - sb.append(msg.getFormattedMessage()); - final Map mdc = ThreadContext.getImmutableContext(); - if (mdc.size() > 0) { - sb.append(' '); - sb.append(mdc.toString()); - sb.append(' '); - } - final Object[] params = msg.getParameters(); - 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; - } - if (t != null) { - sb.append(' '); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - t.printStackTrace(new PrintStream(baos)); - sb.append(baos.toString()); - } - list.add(sb.toString()); - //System.out.println(sb.toString()); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String msg) { - return true; - } - - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String msg, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String msg, final Object... p1) { - 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 boolean isEnabled(final Level level, final Marker marker, final CharSequence msg, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Object msg, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Message msg, final Throwable t) { - return true; - } - - @Override - public Level getLevel() { - return Level.ALL; - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java b/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java deleted file mode 100644 index 847fedd81c5..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.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; - -import java.net.URI; - -import org.apache.logging.log4j.spi.LoggerContext; -import org.apache.logging.log4j.spi.LoggerContextFactory; - -/** - * - */ -public class TestLoggerContextFactory implements LoggerContextFactory { - - private static LoggerContext context = new TestLoggerContext(); - - @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, - final boolean currentContext) { - return context; - } - - @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, - final boolean currentContext, final URI configLocation, final String name) { - return context; - } - - @Override - public void removeContext(final LoggerContext context) { - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.java b/log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.java deleted file mode 100644 index f96181a52c8..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.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. - */ -package org.apache.logging.log4j; - -import org.apache.logging.log4j.spi.Provider; - -/** - * Binding for the Log4j API. - */ -public class TestProvider extends Provider { - public TestProvider() { - super(0, "2.6.0", TestLoggerContextFactory.class); - } -} 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/ThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java deleted file mode 100644 index a1d11016fd6..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java +++ /dev/null @@ -1,182 +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.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -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"); - } - - @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)); - 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)); - } - - @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 testPutAll() { - ThreadContext.clearMap(); - // - assertTrue(ThreadContext.isEmpty()); - assertFalse(ThreadContext.containsKey("key")); - final int mapSize = 10; - final Map newMap = new HashMap<>(mapSize); - for (int i = 1; i <= mapSize; i++) { - newMap.put("key" + i, "value" + i); - } - ThreadContext.putAll(newMap); - assertFalse(ThreadContext.isEmpty()); - for (int i = 1; i <= mapSize; i++) { - assertTrue(ThreadContext.containsKey("key" + i)); - assertEquals("value" + i, ThreadContext.get("key" + i)); - } - } - - @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 testRemoveAll() { - ThreadContext.clearMap(); - ThreadContext.put("testKey1", "testValue1"); - ThreadContext.put("testKey2", "testValue2"); - assertEquals("testValue1", ThreadContext.get("testKey1")); - assertEquals("testValue2", ThreadContext.get("testKey2")); - assertFalse(ThreadContext.isEmpty()); - - ThreadContext.removeAll(Arrays.asList("testKey1", "testKey2")); - assertNull(ThreadContext.get("testKey1")); - assertNull(ThreadContext.get("testKey2")); - 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/Timer.java b/log4j-api/src/test/java/org/apache/logging/log4j/Timer.java deleted file mode 100644 index 5dbe83704b2..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/Timer.java +++ /dev/null @@ -1,270 +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 java.text.DecimalFormat; - -import org.apache.logging.log4j.util.StringBuilderFormattable; -import org.apache.logging.log4j.util.Strings; - -/** - * - */ -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 - 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; - - - /** - * Constructor. - * @param name the timer name. - */ - public Timer(final String name) - { - this(name, 0); - } - - /** - * Constructor. - * - * @param name the timer name. - */ - public Timer(final String name, final int iterations) - { - this.name = name; - startTime = 0; - status = "Stopped"; - this.iterations = (iterations > 0) ? iterations : 0; - } - - /** - * Start the timer. - */ - public void start() - { - startTime = System.nanoTime(); - elapsedTime = 0; - status = "Start"; - } - - /** - * Stop the timer. - */ - public void stop() - { - elapsedTime += System.nanoTime() - startTime; - startTime = 0; - status = "Stop"; - } - - /** - * Pause the timer. - */ - public void pause() - { - elapsedTime += System.nanoTime() - startTime; - startTime = 0; - status = "Pause"; - } - - /** - * Resume the timer. - */ - public void resume() - { - startTime = System.nanoTime(); - status = "Resume"; - } - - /** - * Accessor for the name. - * @return the timer's name. - */ - public String getName() - { - return name; - } - - /** - * Access the elapsed time. - * - * @return the elapsed time. - */ - public long getElapsedTime() - { - return elapsedTime / 1000000; - } - - /** - * Access the elapsed time. - * - * @return the elapsed time. - */ - public long getElapsedNanoTime() - { - return elapsedTime; - } - - /** - * Returns the name of the last operation performed on this timer (Start, Stop, Pause or - * Resume). - * @return the string representing the last operation performed. - */ - public String getStatus() - { - return status; - } - - /** - * Returns the String representation of the timer based upon its current state - */ - @Override - public String toString() - { - final StringBuilder result = new StringBuilder(); - formatTo(result); - return result.toString(); - } - - @Override - public void formatTo(final StringBuilder buffer) { - buffer.append("Timer ").append(name); - switch (status) { - case "Start": - buffer.append(" started"); - break; - case "Pause": - buffer.append(" paused"); - break; - case "Resume": - buffer.append(" resumed"); - break; - case "Stop": - long nanoseconds = elapsedTime; - // Get elapsed hours - long hours = nanoseconds / NANO_PER_HOUR; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_HOUR; - // Get minutes - long minutes = nanoseconds / NANO_PER_MINUTE; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_MINUTE; - // Get seconds - long seconds = nanoseconds / NANO_PER_SECOND; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_SECOND; - - String elapsed = Strings.EMPTY; - - if (hours > 0) { - elapsed += hours + " hours "; - } - if (minutes > 0 || hours > 0) { - elapsed += minutes + " minutes "; - } - - DecimalFormat numFormat; - numFormat = new DecimalFormat("#0"); - elapsed += numFormat.format(seconds) + '.'; - numFormat = new DecimalFormat("000000000"); - elapsed += numFormat.format(nanoseconds) + " seconds"; - buffer.append(" stopped. Elapsed time: ").append(elapsed); - if (iterations > 0) { - nanoseconds = elapsedTime / iterations; - // Get elapsed hours - hours = nanoseconds / NANO_PER_HOUR; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_HOUR; - // Get minutes - minutes = nanoseconds / NANO_PER_MINUTE; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_MINUTE; - // Get seconds - seconds = nanoseconds / NANO_PER_SECOND; - // Get remaining nanoseconds - nanoseconds = nanoseconds % NANO_PER_SECOND; - - elapsed = Strings.EMPTY; - - if (hours > 0) { - elapsed += hours + " hours "; - } - if (minutes > 0 || hours > 0) { - elapsed += minutes + " minutes "; - } - - numFormat = new DecimalFormat("#0"); - elapsed += numFormat.format(seconds) + '.'; - numFormat = new DecimalFormat("000000000"); - elapsed += numFormat.format(nanoseconds) + " seconds"; - buffer.append(" Average per iteration: ").append(elapsed); - } - break; - default: - buffer.append(' ').append(status); - break; - } - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Timer)) { - return false; - } - - final Timer timer = (Timer) o; - - if (elapsedTime != timer.elapsedTime) { - return false; - } - if (startTime != timer.startTime) { - return false; - } - 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; - } - - @Override - 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)); - result = 29 * result + (int) (elapsedTime ^ (elapsedTime >>> 32)); - return result; - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java deleted file mode 100644 index 81a2ca1ea8c..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java +++ /dev/null @@ -1,316 +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.message.DefaultFlowMessageFactory; -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.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.message.ReusableParameterizedMessage; -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 static org.junit.Assert.*; - -/** - * - */ -public class TraceLoggingTest extends AbstractLogger { - static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq"); - private int charSeqCount; - private int objectCount; - - 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 final long serialVersionUID = 1L; - - private static Level currentLevel; - - private LogEvent currentEvent; - - 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 String marker = "TEST"; - - @Override - public Level getLevel() { - return currentLevel; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { - return true; -// 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)); - } - } - } - - @Test - public void testTraceEntryExit() { - currentLevel = Level.TRACE; - final FlowMessageFactory fact = new DefaultFlowMessageFactory(); - - final ParameterizedMessage paramMsg = new ParameterizedMessage("Tracy {}", "Logan"); - currentEvent = new LogEvent(ENTRY_MARKER.getName(), fact.newEntryMessage(paramMsg), null); - final EntryMessage entry = traceEntry("Tracy {}", "Logan"); - - final ReusableParameterizedMessage msg = ReusableParameterizedMessageTest.set( - new ReusableParameterizedMessage(), "Tracy {}", "Logan"); - ReusableParameterizedMessageTest.set(msg, "Some other message {}", 123); - currentEvent = new LogEvent(null, msg, null); - trace("Some other message {}", 123); - - // ensure original entry message not overwritten - assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); - - currentEvent = new LogEvent(EXIT_MARKER.getName(), fact.newExitMessage(entry), null); - traceExit(entry); - - // ensure original entry message not overwritten - assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); - } - - @Test - public void testTraceEntryMessage() { - currentLevel = Level.TRACE; - final FlowMessageFactory fact = new DefaultFlowMessageFactory(); - - final ParameterizedMessage paramMsg = new ParameterizedMessage("Tracy {}", "Logan"); - currentEvent = new LogEvent(ENTRY_MARKER.getName(), fact.newEntryMessage(paramMsg), null); - - final ReusableParameterizedMessage msg = ReusableParameterizedMessageTest.set( - new ReusableParameterizedMessage(), "Tracy {}", "Logan"); - final EntryMessage entry = traceEntry(msg); - - ReusableParameterizedMessageTest.set(msg, "Some other message {}", 123); - currentEvent = new LogEvent(null, msg, null); - trace("Some other message {}", 123); - - // ensure original entry message not overwritten - assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); - - currentEvent = new LogEvent(EXIT_MARKER.getName(), fact.newExitMessage(entry), null); - traceExit(entry); - - // ensure original entry message not overwritten - assertEquals("Tracy Logan", entry.getMessage().getFormattedMessage()); - } -} 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/junit/Mutable.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/Mutable.java deleted file mode 100644 index b81a12b420d..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/Mutable.java +++ /dev/null @@ -1,35 +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; - -/** - * Helper class for JUnit tests. - */ -public class Mutable { - private String value; - - public Mutable set(final String value) { - this.value = value; - return this; - } - - @Override - public String toString() { - return this.value; - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.java deleted file mode 100644 index 211eca6b44d..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.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.junit; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; - -/** - * Utility class to facilitate serializing and deserializing objects. - */ -public class SerialUtil { - private SerialUtil() { - } - - /** - * Serializes the specified object and returns the result as a byte array. - * @param obj the object to serialize - * @return the serialized object - */ - public static byte[] serialize(final Serializable obj) { - try { - final ByteArrayOutputStream bas = new ByteArrayOutputStream(8192); - final ObjectOutputStream oos = new ObjectOutputStream(bas); - oos.writeObject(obj); - oos.flush(); - return bas.toByteArray(); - } catch (final Exception ex) { - throw new IllegalStateException("Could not serialize", ex); - } - } - - /** - * Deserialize an object from the specified byte array and returns the result. - * @param data byte array representing the serialized object - * @return the deserialized object - */ - @SuppressWarnings("unchecked") - public static T deserialize(final byte[] data) { - try { - final ByteArrayInputStream bas = new ByteArrayInputStream(data); - final ObjectInputStream ois = new ObjectInputStream(bas); - return (T) ois.readObject(); - } catch (final Exception ex) { - throw new IllegalStateException("Could not deserialize", ex); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java deleted file mode 100644 index 406630382af..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.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.junit; - -/** - * Restores the ThreadContext to it's initial map values after a JUnit test. - * - * Usage: - * - *
- * @Rule
- * public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule();
- * 
- */ -public class ThreadContextMapRule extends ThreadContextRule { - - /** - * Constructs an initialized instance. - */ - public ThreadContextMapRule() { - super(true, false); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java deleted file mode 100644 index 1581dc8a7c4..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.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.junit; - -/** - * Restores the ThreadContext to it's initial stack values after a JUnit test. - * - * Usage: - * - *
- * @Rule
- * public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule();
- * 
- */ -public class ThreadContextStackRule extends ThreadContextRule { - - /** - * Constructs an initialized instance. - */ - public ThreadContextStackRule() { - super(false, true); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java deleted file mode 100644 index 937d6593e51..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java +++ /dev/null @@ -1,175 +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.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.apache.logging.log4j.util.Constants; -import org.junit.Assert; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * - */ -public class FormattedMessageTest { - - private static final String SPACE = Constants.JAVA_MAJOR_VERSION < 9 ? " " : "\u00a0"; - - private static final int LOOP_CNT = 500; - String[] array = new String[LOOP_CNT]; - - @Test - public void testStringNoArgs() { - final String testMsg = "Test message %1s"; - FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - final String expected = "Test message null"; - assertEquals(expected, result); - final Object[] array = null; - msg = new FormattedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(expected, result); - } - - @Test - public void tesStringOneStringArg() { - final String testMsg = "Test message %1s"; - final FormattedMessage msg = new FormattedMessage(testMsg, "Apache"); - final String result = msg.getFormattedMessage(); - final String expected = "Test message Apache"; - assertEquals(expected, result); - } - - @Test - public void tesStringOneArgLocaleFrance_StringFormattedMessage() { - final String testMsg = "Test message e = %+10.4f"; - final FormattedMessage msg = new FormattedMessage(Locale.FRANCE, testMsg, Math.E); - final String result = msg.getFormattedMessage(); - final String expected = "Test message e = +2,7183"; - assertEquals(expected, result); - } - - @Test - public void tesStringOneArgLocaleFrance_MessageFormatMessage() { - final String testMsg = "Test message {0,number,currency}"; - final FormattedMessage msg = new FormattedMessage(Locale.FRANCE, testMsg, 12); - final String result = msg.getFormattedMessage(); - final String expected = "Test message 12,00" + SPACE +"€"; - assertEquals(expected, result); - } - - @Test - public void tesStringOneArgLocaleUs_MessageFormatMessage() { - final String testMsg = "Test message {0,number,currency}"; - final FormattedMessage msg = new FormattedMessage(Locale.US, testMsg, 12); - final String result = msg.getFormattedMessage(); - final String expected = "Test message $12.00"; - assertEquals(expected, result); - } - - @Test - public void tesStringOneArgLocaleUs() { - final String testMsg = "Test message e = %+10.4f"; - final FormattedMessage msg = new FormattedMessage(Locale.US, testMsg, Math.E); - final String result = msg.getFormattedMessage(); - final String expected = "Test message e = +2.7183"; - assertEquals(expected, result); - } - - @Test - public void testNoArgs() { - final String testMsg = "Test message {0}"; - FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - final String expected = "Test message {0}"; - assertEquals(expected, result); - final Object[] array = null; - msg = new FormattedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(expected, result); - } - - @Test - public void testOneArg() { - final String testMsg = "Test message {0}"; - final FormattedMessage msg = new FormattedMessage(testMsg, "Apache"); - final String result = msg.getFormattedMessage(); - final String expected = "Test message Apache"; - assertEquals(expected, result); - } - - @Test - public void testParamNoArgs() { - final String testMsg = "Test message {}"; - FormattedMessage msg = new FormattedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - final Object[] array = null; - msg = new FormattedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - } - - @Test - public void testUnsafeWithMutableParams() { // LOG4J2-763 - final String testMsg = "Test message %s"; - final Mutable param = new Mutable().set("abc"); - final FormattedMessage msg = new FormattedMessage(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent 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 FormattedMessage msg = new FormattedMessage(testMsg, param); - - // modify parameter after calling msg.getFormattedMessage - msg.getFormattedMessage(); // freeze the formatted message - 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 FormattedMessage expected = new FormattedMessage("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 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()); - } -} 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/LocalizedMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java deleted file mode 100644 index aebe6ed1006..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java +++ /dev/null @@ -1,89 +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.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.*; - -/** - * Tests LocalizedMessage. - */ -public class LocalizedMessageTest { - - private T roundtrip(final T msg) { - return SerializationUtils.roundtrip(msg); - } - - @Test - public void testMessageFormat() { - final LocalizedMessage msg = new LocalizedMessage("MF", new Locale("en", "US"), "msg1", new Object[] { "1", "Test" }); - assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); - } - - @Test - public void testSerializationMessageFormat() { - final LocalizedMessage msg = new LocalizedMessage("MF", new Locale("en", "US"), "msg1", new Object[] { "1", "Test" }); - assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); - final LocalizedMessage msg2 = roundtrip(msg); - assertEquals("This is test number 1 with string argument Test.", msg2.getFormattedMessage()); - } - - @Test - public void testSerializationStringFormat() { - final LocalizedMessage msg = new LocalizedMessage("SF", new Locale("en", "US"), "msg1", new Object[] { "1", "Test" }); - assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); - final LocalizedMessage msg2 = roundtrip(msg); - assertEquals("This is test number 1 with string argument Test.", msg2.getFormattedMessage()); - } - - @Test - public void testStringFormat() { - final LocalizedMessage msg = new LocalizedMessage("SF", new Locale("en", "US"), "msg1", new Object[] { "1", "Test" }); - assertEquals("This is test number 1 with string argument Test.", msg.getFormattedMessage()); - } - - @Test - public void testUnsafeWithMutableParams() { // LOG4J2-763 - final String testMsg = "Test message %s"; - final Mutable param = new Mutable().set("abc"); - final LocalizedMessage msg = new LocalizedMessage(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent 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 LocalizedMessage msg = new LocalizedMessage(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); - } -} 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/MessageFormatMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java deleted file mode 100644 index d123b74e1ad..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java +++ /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. - */ - -package org.apache.logging.log4j.message; - -import org.apache.logging.log4j.junit.Mutable; -import org.apache.logging.log4j.util.Constants; -import org.junit.Test; - -import static org.junit.Assert.*; - -import java.util.Locale; - -/** - * - */ -public class MessageFormatMessageTest { - - private static final String SPACE = Constants.JAVA_MAJOR_VERSION < 9 ? " " : "\u00a0"; - - private static final int LOOP_CNT = 500; - String[] array = new String[LOOP_CNT]; - - @Test - public void testNoArgs() { - final String testMsg = "Test message {0}"; - MessageFormatMessage msg = new MessageFormatMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - String expected = "Test message {0}"; - assertEquals(expected, result); - final Object[] array = null; - msg = new MessageFormatMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - expected = "Test message null"; - assertEquals(expected, result); - } - - @Test - public void testOneStringArg() { - final String testMsg = "Test message {0}"; - final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache"); - final String result = msg.getFormattedMessage(); - final String expected = "Test message Apache"; - assertEquals(expected, result); - } - - @Test - public void testOneIntArgLocaleUs() { - final String testMsg = "Test message {0,number,currency}"; - final MessageFormatMessage msg = new MessageFormatMessage(Locale.US, testMsg, 1234567890); - final String result = msg.getFormattedMessage(); - final String expected = "Test message $1,234,567,890.00"; - assertEquals(expected, result); - } - - @Test - 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 + "€"; - 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 {0}"; - final Mutable param = new Mutable().set("abc"); - final MessageFormatMessage msg = new MessageFormatMessage(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "Test message XYZ", actual); - } - - @Test - public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 - final String testMsg = "Test message {0}"; - final Mutable param = new Mutable().set("abc"); - final MessageFormatMessage msg = new MessageFormatMessage(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); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java deleted file mode 100644 index a151eac08d1..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java +++ /dev/null @@ -1,121 +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.Timer; -import org.junit.AfterClass; -import org.junit.Test; - -/** - * - */ -public class MessageFormatsPerfTest { - - private static final int LOOP_CNT = 500; - String[] array = new String[LOOP_CNT]; - private static long stringTime = 0; - private static long paramTime = 0; - private static long msgFormatTime = 0; - private static long formattedTime = 0; - - @AfterClass - public static void after() { - if (stringTime > paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times faster than StringFormat.", - ((float) stringTime / paramTime))); - } else if (stringTime < paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times slower than StringFormat.", - ((float) paramTime / stringTime))); - } else { - System.out.println("The speed of Parameterized and StringFormat are the same"); - } - if (msgFormatTime > paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times faster than MessageFormat.", - ((float) msgFormatTime / paramTime))); - } else if (msgFormatTime < paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times slower than MessageFormat.", - ((float) paramTime / msgFormatTime))); - } else { - System.out.println("The speed of Parameterized and MessageFormat are the same"); - } - if (formattedTime > paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times faster than Formatted.", - ((float) formattedTime / paramTime))); - } else if (formattedTime < paramTime) { - System.out.println(String.format("Parameterized is %1$.2f times slower than Formatted.", - ((float) paramTime / formattedTime))); - } else { - System.out.println("The speed of Parameterized and Formatted are the same"); - } - } - - @Test - public void testStringPerf() { - final String testMsg = "Test message %1s %2s"; - final Timer timer = new Timer("StringFormat", LOOP_CNT); - timer.start(); - for (int i = 0; i < LOOP_CNT; ++i) { - final StringFormattedMessage msg = new StringFormattedMessage(testMsg, "Apache", "Log4j"); - array[i] = msg.getFormattedMessage(); - } - timer.stop(); - stringTime = timer.getElapsedNanoTime(); - System.out.println(timer.toString()); - } - - @Test - public void testMessageFormatPerf() { - final String testMsg = "Test message {0} {1}"; - final Timer timer = new Timer("MessageFormat", LOOP_CNT); - timer.start(); - for (int i = 0; i < LOOP_CNT; ++i) { - final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache", "Log4j"); - array[i] = msg.getFormattedMessage(); - } - timer.stop(); - msgFormatTime = timer.getElapsedNanoTime(); - System.out.println(timer.toString()); - } - - @Test - public void testParameterizedPerf() { - final String testMsg = "Test message {} {}"; - final Timer timer = new Timer("Parameterized", LOOP_CNT); - timer.start(); - for (int i = 0; i < LOOP_CNT; ++i) { - final ParameterizedMessage msg = new ParameterizedMessage(testMsg, "Apache", "Log4j"); - array[i] = msg.getFormattedMessage(); - } - timer.stop(); - paramTime = timer.getElapsedNanoTime(); - System.out.println(timer.toString()); - } - - @Test - public void testFormattedParameterizedPerf() { - final String testMsg = "Test message {} {}"; - final Timer timer = new Timer("FormattedParameterized", LOOP_CNT); - timer.start(); - for (int i = 0; i < LOOP_CNT; ++i) { - final FormattedMessage msg = new FormattedMessage(testMsg, "Apache", "Log4j"); - array[i] = msg.getFormattedMessage(); - } - timer.stop(); - formattedTime = timer.getElapsedNanoTime(); - System.out.println(timer.toString()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java deleted file mode 100644 index 5f11cf8204f..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.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 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()); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java deleted file mode 100644 index 7f28ddb7c2f..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.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; - -class ObjectMapMessage extends MapMessage { - - private static final long serialVersionUID = 1L; - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java deleted file mode 100644 index 8f547ae5081..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java +++ /dev/null @@ -1,109 +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.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 static org.junit.Assert.*; - -/** - * Tests {@link ObjectMessage}. - */ -public class ObjectMessageTest { - - @Test - public void testNull() { - final ObjectMessage msg = new ObjectMessage(null); - final String result = msg.getFormattedMessage(); - assertEquals("null", result); - } - - @Test - public void testNotNull() { - final String testMsg = "Test message {}"; - final ObjectMessage msg = new ObjectMessage(testMsg); - final String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - } - - @Test - public void testUnsafeWithMutableParams() { // LOG4J2-763 - final Mutable param = new Mutable().set("abc"); - final ObjectMessage msg = new ObjectMessage(param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "XYZ", actual); - } - - @Test - public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 - final Mutable param = new Mutable().set("abc"); - final ObjectMessage msg = new ObjectMessage(param); - - // modify parameter after calling msg.getFormattedMessage - msg.getFormattedMessage(); - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "abc", actual); - } - - @Test - public void testSerializeWithSerializableParam() { - final BigDecimal big = BigDecimal.valueOf(123.456); - final ObjectMessage msg = new ObjectMessage(big); - final ObjectMessage other = SerialUtil.deserialize(SerialUtil.serialize(msg)); - assertEquals(msg, other); - } - - @Test - public void testDeserializeNonSerializableParamEqualIfToStringSame() { - class NonSerializable { - @Override - public boolean equals(final Object other) { - return other instanceof NonSerializable; // a very lenient equals() - } - } - final NonSerializable nonSerializable = new NonSerializable(); - assertFalse(nonSerializable instanceof Serializable); - final ObjectMessage msg = new ObjectMessage(nonSerializable); - final ObjectMessage other = SerialUtil.deserialize(SerialUtil.serialize(msg)); - - assertEquals(msg, other); - assertEquals(other, msg); - } - - @Test - public void formatTo_usesCachedMessageString() throws Exception { - final StringBuilder charSequence = new StringBuilder("initial value"); - final ObjectMessage message = new ObjectMessage(charSequence); - assertEquals("initial value", message.getFormattedMessage()); - - charSequence.setLength(0); - charSequence.append("different value"); - - final StringBuilder result = new StringBuilder(); - message.formatTo(result); - assertEquals("initial value", result.toString()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java deleted file mode 100644 index 6e728f6928e..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java +++ /dev/null @@ -1,191 +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.ArrayList; -import java.util.List; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests ParameterFormatter. - */ -public class ParameterFormatterTest { - - @Test - public void testCountArgumentPlaceholders() throws Exception { - assertEquals(0, ParameterFormatter.countArgumentPlaceholders("")); - assertEquals(0, ParameterFormatter.countArgumentPlaceholders("aaa")); - assertEquals(0, ParameterFormatter.countArgumentPlaceholders("\\{}")); - assertEquals(1, ParameterFormatter.countArgumentPlaceholders("{}")); - assertEquals(1, ParameterFormatter.countArgumentPlaceholders("{}\\{}")); - assertEquals(2, ParameterFormatter.countArgumentPlaceholders("{}{}")); - assertEquals(3, ParameterFormatter.countArgumentPlaceholders("{}{}{}")); - assertEquals(4, ParameterFormatter.countArgumentPlaceholders("{}{}{}aa{}")); - assertEquals(4, ParameterFormatter.countArgumentPlaceholders("{}{}{}a{]b{}")); - assertEquals(5, ParameterFormatter.countArgumentPlaceholders("{}{}{}a{}b{}")); - } - - @Test - public void testFormat3StringArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatNullArgs() { - final String testMsg = "Test message {} {} {} {} {} {}"; - final String[] args = { "a", null, "c", null, null, null }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message a null c null null null", result); - } - - @Test - public void testFormatStringArgsIgnoresSuperfluousArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c", "unnecessary", "superfluous" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatStringArgsWithEscape() { - final String testMsg = "Test message \\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message {}a b", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscape() { - final String testMsg = "Test message {}{} {}\\"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message ab c\\", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscapedEscape() { - final String testMsg = "Test message {}{} {}\\\\"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message ab c\\\\", result); - } - - @Test - public void testFormatStringArgsWithEscapedEscape() { - final String testMsg = "Test message \\\\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterFormatter.format(testMsg, args); - assertEquals("Test message \\ab c", result); - } - - @Test - public void testFormatMessage3StringArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 3); - final String result = sb.toString(); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatMessageNullArgs() { - final String testMsg = "Test message {} {} {} {} {} {}"; - final String[] args = { "a", null, "c", null, null, null }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 6); - final String result = sb.toString(); - assertEquals("Test message a null c null null null", result); - } - - @Test - public void testFormatMessageStringArgsIgnoresSuperfluousArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c", "unnecessary", "superfluous" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 5); - final String result = sb.toString(); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatMessageStringArgsWithEscape() { - final String testMsg = "Test message \\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 3); - final String result = sb.toString(); - assertEquals("Test message {}a b", result); - } - - @Test - public void testFormatMessageStringArgsWithTrailingEscape() { - final String testMsg = "Test message {}{} {}\\"; - final String[] args = { "a", "b", "c" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 3); - final String result = sb.toString(); - assertEquals("Test message ab c\\", result); - } - - @Test - public void testFormatMessageStringArgsWithTrailingEscapedEscape() { - final String testMsg = "Test message {}{} {}\\\\"; - final String[] args = { "a", "b", "c" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 3); - final String result = sb.toString(); - assertEquals("Test message ab c\\\\", result); - } - - @Test - public void testFormatMessageStringArgsWithEscapedEscape() { - final String testMsg = "Test message \\\\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final StringBuilder sb = new StringBuilder(); - ParameterFormatter.formatMessage(sb, testMsg, args, 3); - final String result = sb.toString(); - assertEquals("Test message \\ab c", result); - } - - @Test - public void testDeepToString() throws Exception { - final List list = new ArrayList<>(); - list.add(1); - list.add(list); - list.add(2); - final String actual = ParameterFormatter.deepToString(list); - final String expected = "[1, [..." + ParameterFormatter.identityToString(list) + "...], 2]"; - assertEquals(expected, actual); - } - - @Test - public void testIdentityToString() throws Exception { - final List list = new ArrayList<>(); - list.add(1); - 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/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java deleted file mode 100644 index 2a5bac6ceb3..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.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.message; - -import org.apache.logging.log4j.junit.Mutable; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class ParameterizedMessageTest { - - @Test - public void testNoArgs() { - final String testMsg = "Test message {}"; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - } - - @Test - public void testZeroLength() { - final String testMsg = ""; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[]{"arg"}); - String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - } - - @Test - public void testOneCharLength() { - final String testMsg = "d"; - ParameterizedMessage msg = new ParameterizedMessage(testMsg, new Object[]{"arg"}); - String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - final Object[] array = null; - msg = new ParameterizedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - } - - @Test - public void testFormat3StringArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatNullArgs() { - final String testMsg = "Test message {} {} {} {} {} {}"; - final String[] args = { "a", null, "c", null, null, null }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message a null c null null null", result); - } - - @Test - public void testFormatStringArgsIgnoresSuperfluousArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c", "unnecessary", "superfluous" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatStringArgsWithEscape() { - final String testMsg = "Test message \\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message {}a b", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscape() { - final String testMsg = "Test message {}{} {}\\"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message ab c\\", result); - } - - @Test - public void testFormatStringArgsWithTrailingText() { - final String testMsg = "Test message {}{} {}Text"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message ab cText", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscapedEscape() { - final String testMsg = "Test message {}{} {}\\\\"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message ab c\\\\", result); - } - - @Test - public void testFormatStringArgsWithEscapedEscape() { - final String testMsg = "Test message \\\\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = ParameterizedMessage.format(testMsg, args); - assertEquals("Test message \\ab c", result); - } - - @Test - public void testSafeWithMutableParams() { // LOG4J2-763 - final String testMsg = "Test message {}"; - final Mutable param = new Mutable().set("abc"); - final ParameterizedMessage msg = new ParameterizedMessage(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Should use current param value", "Test message XYZ", actual); - - // 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); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java deleted file mode 100644 index 7942e5faf55..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java +++ /dev/null @@ -1,130 +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.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the ReusableMessageFactory class. - */ -public class ReusableMessageFactoryTest { - - @Test - public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); - final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); - final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); - assertNotSame(message1, message2); - ReusableMessageFactory.release(message1); - ReusableMessageFactory.release(message2); - } - - @Test - public void testCreateEventReturnsSameInstance() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); - final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); - - ReusableMessageFactory.release(message1); - final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6); - assertSame(message1, message2); - - ReusableMessageFactory.release(message2); - final Message message3 = factory.newMessage("text, AAA={} BBB={} p2={} p3={}", 9, 8, 7, 6); - assertSame(message2, message3); - ReusableMessageFactory.release(message3); - } - - 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); - - assertEquals(txt, msg.getFormat()); - assertEquals("count", msg.getParameterCount(), params.length); - final Object[] messageParams = msg.getParameters(); - for (int i = 0; i < params.length; i++) { - assertEquals(messageParams[i], params[i]); - } - } - - @Test - public void testCreateEventOverwritesFields() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); - final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); - assertReusableParameterizeMessage(message1, "text, p0={} p1={} p2={} p3={}", new Object[]{ - new Integer(1), // - new Integer(2), // - new Integer(3), // - new Integer(4), // - }); - - ReusableMessageFactory.release(message1); - final Message message2 = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4); - assertReusableParameterizeMessage(message1, "other, A={} B={} C={} D={}", new Object[]{ - new Integer(1), // - new Integer(2), // - new Integer(3), // - new Integer(4), // - }); - assertSame(message1, message2); - ReusableMessageFactory.release(message2); - } - - @Test - public void testCreateEventReturnsThreadLocalInstance() throws Exception { - final ReusableMessageFactory factory = new ReusableMessageFactory(); - final Message[] message1 = new Message[1]; - final Message[] message2 = new Message[1]; - final Thread t1 = new Thread("THREAD 1") { - @Override - public void run() { - message1[0] = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4); - } - }; - final Thread t2 = new Thread("Thread 2") { - @Override - public void run() { - message2[0] = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4); - } - }; - t1.start(); - t2.start(); - t1.join(); - t2.join(); - assertNotNull(message1[0]); - assertNotNull(message2[0]); - assertNotSame(message1[0], message2[0]); - assertReusableParameterizeMessage(message1[0], "text, p0={} p1={} p2={} p3={}", new Object[]{ - new Integer(1), // - new Integer(2), // - new Integer(3), // - new Integer(4), // - }); - - assertReusableParameterizeMessage(message2[0], "other, A={} B={} C={} D={}", new Object[]{ - new Integer(1), // - new Integer(2), // - new Integer(3), // - new Integer(4), // - }); - ReusableMessageFactory.release(message1[0]); - 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/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java deleted file mode 100644 index d4f87a7dd8e..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java +++ /dev/null @@ -1,122 +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.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests ReusableObjectMessage. - */ -public class ReusableObjectMessageTest { - - @Test - public void testSet_InitializesFormattedMessage() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormattedMessage()); - } - - @Test - public void testGetFormattedMessage_InitiallyNullString() throws Exception { - assertEquals("null", new ReusableObjectMessage().getFormattedMessage()); - } - - @Test - public void testGetFormattedMessage_ReturnsLatestSetString() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormattedMessage()); - msg.set("def"); - assertEquals("def", msg.getFormattedMessage()); - msg.set("xyz"); - assertEquals("xyz", msg.getFormattedMessage()); - } - - @Test - public void testGetFormat_InitiallyNullString() throws Exception { - assertEquals("null", new ReusableObjectMessage().getFormat()); - } - - @Test - public void testGetFormat_ReturnsLatestSetString() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormat()); - msg.set("def"); - assertEquals("def", msg.getFormat()); - msg.set("xyz"); - assertEquals("xyz", msg.getFormat()); - } - - @Test - public void testGetParameters_InitiallyReturnsNullObjectInLength1Array() throws Exception { - assertArrayEquals(new Object[]{null}, new ReusableObjectMessage().getParameters()); - } - - @Test - public void testGetParameters_ReturnsSetObjectInParameterArrayAfterMessageSet() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - msg.set("abc"); - assertArrayEquals(new Object[]{"abc"}, msg.getParameters()); - msg.set("def"); - assertArrayEquals(new Object[]{"def"}, msg.getParameters()); - } - - @Test - public void testGetThrowable_InitiallyReturnsNull() throws Exception { - assertNull(new ReusableObjectMessage().getThrowable()); - } - - @Test - public void testGetThrowable_ReturnsNullAfterMessageSet() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - msg.set("abc"); - assertNull(msg.getThrowable()); - msg.set("def"); - assertNull(msg.getThrowable()); - } - - @Test - public void testFormatTo_InitiallyWritesNull() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - final StringBuilder sb = new StringBuilder(); - msg.formatTo(sb); - assertEquals("null", sb.toString()); - } - - @Test - public void testFormatTo_WritesLatestSetString() throws Exception { - final ReusableObjectMessage msg = new ReusableObjectMessage(); - final StringBuilder sb = new StringBuilder(); - msg.formatTo(sb); - assertEquals("null", sb.toString()); - sb.setLength(0); - msg.set("abc"); - msg.formatTo(sb); - assertEquals("abc", sb.toString()); - sb.setLength(0); - msg.set("def"); - msg.formatTo(sb); - assertEquals("def", sb.toString()); - sb.setLength(0); - msg.set("xyz"); - 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/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java deleted file mode 100644 index b25735c17af..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java +++ /dev/null @@ -1,147 +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.junit.Mutable; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests ReusableParameterizedMessage. - */ -public class ReusableParameterizedMessageTest { - - public static ReusableParameterizedMessage set(final ReusableParameterizedMessage msg, final String format, - final Object... params) { - - return msg.set(format, params); - } - - @Test - public void testNoArgs() { - final String testMsg = "Test message {}"; - final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); - msg.set(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - assertEquals(testMsg, result); - - msg.set(testMsg, (Object) null); - result = msg.getFormattedMessage(); - assertEquals("Test message null", result); - - msg.set(testMsg, null, null); - result = msg.getFormattedMessage(); - assertEquals("Test message null", result); - } - - @Test - public void testFormat3StringArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatNullArgs() { - final String testMsg = "Test message {} {} {} {} {} {}"; - final String[] args = { "a", null, "c", null, null, null }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message a null c null null null", result); - } - - @Test - public void testFormatStringArgsIgnoresSuperfluousArgs() { - final String testMsg = "Test message {}{} {}"; - final String[] args = { "a", "b", "c", "unnecessary", "superfluous" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message ab c", result); - } - - @Test - public void testFormatStringArgsWithEscape() { - final String testMsg = "Test message \\{}{} {}"; - final String[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message {}a b", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscape() { - final String testMsg = "Test message {}{} {}\\"; - final String[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message ab c\\", result); - } - - @Test - public void testFormatStringArgsWithTrailingText() { - final String testMsg = "Test message {}{} {}Text"; - final String[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message ab cText", result); - } - - @Test - public void testFormatStringArgsWithTrailingEscapedEscape() { - final String testMsg = "Test message {}{} {}\\\\"; - final String[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, (Object[]) args).getFormattedMessage(); - assertEquals("Test message ab c\\\\", result); - } - - @Test - public void testFormatStringArgsWithEscapedEscape() { - final String testMsg = "Test message \\\\{}{} {}"; - final Object[] args = { "a", "b", "c" }; - final String result = new ReusableParameterizedMessage().set(testMsg, args).getFormattedMessage(); - assertEquals("Test message \\ab c", result); - } - - - @Test - public void testNotSafeWithMutableParams() { - final String testMsg = "Test message {}"; - final Mutable param = new Mutable().set("abc"); - final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); - msg.set(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Should use current param value", "Test message XYZ", actual); - - // modify parameter after calling msg.getFormattedMessage - param.set("000"); - final String after = msg.getFormattedMessage(); - assertEquals("Renders again", "Test message 000", after); - } - - @Test - public void testThrowable() { - final String testMsg = "Test message {}"; - final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); - final Throwable EXCEPTION1 = new IllegalAccessError("#1"); - msg.set(testMsg, "msg", EXCEPTION1); - assertSame(EXCEPTION1, msg.getThrowable()); - - final Throwable EXCEPTION2 = new UnsupportedOperationException("#2"); - msg.set(testMsg, "msgs", EXCEPTION2); - assertSame(EXCEPTION2, msg.getThrowable()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java deleted file mode 100644 index 3a56b643175..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java +++ /dev/null @@ -1,122 +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.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests ReusableSimpleMessage. - */ -public class ReusableSimpleMessageTest { - - @Test - public void testSet_InitializesFormattedMessage() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormattedMessage()); - } - - @Test - public void testGetFormattedMessage_InitiallyStringNull() throws Exception { - assertEquals("null", new ReusableSimpleMessage().getFormattedMessage()); - } - - @Test - public void testGetFormattedMessage_ReturnsLatestSetString() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormattedMessage()); - msg.set("def"); - assertEquals("def", msg.getFormattedMessage()); - msg.set("xyz"); - assertEquals("xyz", msg.getFormattedMessage()); - } - - @Test - public void testGetFormat_InitiallyStringNull() throws Exception { - assertEquals("null", new ReusableSimpleMessage().getFormat()); - } - - @Test - public void testGetFormat_ReturnsLatestSetString() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - msg.set("abc"); - assertEquals("abc", msg.getFormat()); - msg.set("def"); - assertEquals("def", msg.getFormat()); - msg.set("xyz"); - assertEquals("xyz", msg.getFormat()); - } - - @Test - public void testGetParameters_InitiallyReturnsEmptyArray() throws Exception { - assertArrayEquals(new Object[0], new ReusableSimpleMessage().getParameters()); - } - - @Test - public void testGetParameters_ReturnsEmptyArrayAfterMessageSet() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - msg.set("abc"); - assertArrayEquals(new Object[0], msg.getParameters()); - msg.set("def"); - assertArrayEquals(new Object[0], msg.getParameters()); - } - - @Test - public void testGetThrowable_InitiallyReturnsNull() throws Exception { - assertNull(new ReusableSimpleMessage().getThrowable()); - } - - @Test - public void testGetThrowable_ReturnsNullAfterMessageSet() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - msg.set("abc"); - assertNull(msg.getThrowable()); - msg.set("def"); - assertNull(msg.getThrowable()); - } - - @Test - public void testFormatTo_InitiallyWritesNull() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - final StringBuilder sb = new StringBuilder(); - msg.formatTo(sb); - assertEquals("null", sb.toString()); - } - - @Test - public void testFormatTo_WritesLatestSetString() throws Exception { - final ReusableSimpleMessage msg = new ReusableSimpleMessage(); - final StringBuilder sb = new StringBuilder(); - msg.formatTo(sb); - assertEquals("null", sb.toString()); - sb.setLength(0); - msg.set("abc"); - msg.formatTo(sb); - assertEquals("abc", sb.toString()); - sb.setLength(0); - msg.set("def"); - msg.formatTo(sb); - assertEquals("def", sb.toString()); - sb.setLength(0); - msg.set("xyz"); - 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/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java deleted file mode 100644 index 383ecb49263..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.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 org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the SimpleMessage class. - */ -public class SimpleMessageTest { - @Test - public void formatTo_usesCachedMessageString() throws Exception { - final StringBuilder charSequence = new StringBuilder("initial value"); - final SimpleMessage message = new SimpleMessage(charSequence); - assertEquals("initial value", message.getFormattedMessage()); - - charSequence.setLength(0); - charSequence.append("different value"); - - final StringBuilder result = new StringBuilder(); - message.formatTo(result); - assertEquals("initial value", result.toString()); - } -} 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/message/StructuredDataMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java deleted file mode 100644 index 51a4ceec460..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java +++ /dev/null @@ -1,110 +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.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class StructuredDataMessageTest { - - @Test - public void testMsg() { - final String testMsg = "Test message {}"; - final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); - final String result = msg.getFormattedMessage(); - final String expected = "Alert [MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; - assertEquals(expected, result); - } - - @Test - public void testMsgNonFull() { - final String testMsg = "Test message {}"; - final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); - final String result = msg.getFormattedMessage(new String[] { "WHATEVER" }); - final String expected = "[MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"]"; - assertEquals(expected, result); - } - - @Test - public void testMsgXml() { - final String testMsg = "Test message {}"; - final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - msg.put("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); - final String result = msg.getFormattedMessage(new String[] { "XML" }); - final String expected = - "\n" - + "Alert\n" - + "MsgId@12345\n" - + "\n" - + " This is a very long test memo to prevent regression of LOG4J2-114\n" - + " Test message {}\n" - + " Log4j\n" - + "\n" - + "\n"; - assertEquals(expected, result); - } - - @Test - public void testBuilder() { - final String testMsg = "Test message {}"; - final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert") - .with("message", testMsg) - .with("project", "Log4j") - .with("memo", "This is a very long test memo to prevent regression of LOG4J2-114"); - final String result = msg.getFormattedMessage(); - final String expected = "Alert [MsgId@12345 memo=\"This is a very long test memo to prevent regression of LOG4J2-114\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; - assertEquals(expected, result); - } - - @Test(expected = IllegalArgumentException.class) - 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"); - } - - @Test - public void testMutableByDesign() { // LOG4J2-763 - final String testMsg = "Test message {}"; - final StructuredDataMessage msg = new StructuredDataMessage("MsgId@1", testMsg, "Alert"); - - // modify parameter before calling msg.getFormattedMessage - msg.put("message", testMsg); - msg.put("project", "Log4j"); - final String result = msg.getFormattedMessage(); - final String expected = "Alert [MsgId@1 message=\"Test message {}\" project=\"Log4j\"] Test message {}"; - assertEquals(expected, result); - - // modify parameter after calling msg.getFormattedMessage - msg.put("memo", "Added later"); - final String result2 = msg.getFormattedMessage(); - final String expected2 = "Alert [MsgId@1 memo=\"Added later\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; - assertEquals(expected2, result2); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java deleted file mode 100644 index 8668d0ab84f..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java +++ /dev/null @@ -1,168 +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.concurrent.CountDownLatch; -import java.util.concurrent.locks.ReentrantLock; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class ThreadDumpMessageTest { - - @Test - public void testMessage() { - final ThreadDumpMessage msg = new ThreadDumpMessage("Testing"); - - 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")); - } - - - @Test - public void testMessageWithLocks() throws Exception { - final ReentrantLock lock = new ReentrantLock(); - lock.lock(); - final Thread thread1 = new Thread1(lock); - thread1.start(); - ThreadDumpMessage msg; - synchronized(this) { - final Thread thread2 = new Thread2(this); - thread2.start(); - try { - Thread.sleep(200); - msg = new ThreadDumpMessage("Testing"); - } finally { - lock.unlock(); - } - } - - 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("No Locks", message.contains("waiting on")); - //assertTrue("No syncronizers", message.contains("locked syncrhonizers")); - } - - @Test - public void testToString() { - final ThreadDumpMessage msg = new ThreadDumpMessage("Test"); - final String actual = msg.toString(); - assertTrue(actual.contains("Test")); - assertTrue(actual.contains("RUNNABLE")); - assertTrue(actual.contains(getClass().getName())); - } - - @Test - public void testUseConstructorThread() throws InterruptedException { // LOG4J2-763 - final ThreadDumpMessage msg = new ThreadDumpMessage("Test"); - - final String[] actual = new String[1]; - final Thread other = new Thread("OtherThread") { - @Override - public void run() { - actual[0] = msg.getFormattedMessage(); - } - }; - other.start(); - other.join(); - - assertTrue("No mention of other thread in msg", !actual[0].contains("OtherThread")); - } - - @Test - public void formatTo_usesCachedMessageString() throws Exception { - - final ThreadDumpMessage message = new ThreadDumpMessage(""); - final String initial = message.getFormattedMessage(); - assertFalse("no ThreadWithCountDownLatch thread yet", initial.contains("ThreadWithCountDownLatch")); - - final CountDownLatch started = new CountDownLatch(1); - final CountDownLatch keepAlive = new CountDownLatch(1); - final ThreadWithCountDownLatch thread = new ThreadWithCountDownLatch(started, keepAlive); - thread.start(); - started.await(); // ensure thread is running - - final StringBuilder result = new StringBuilder(); - message.formatTo(result); - assertFalse("no ThreadWithCountDownLatch captured", - result.toString().contains("ThreadWithCountDownLatch")); - assertEquals(initial, result.toString()); - keepAlive.countDown(); // allow thread to die - } - - private class Thread1 extends Thread { - private final ReentrantLock lock; - - public Thread1(final ReentrantLock lock) { - this.lock = lock; - } - - @Override - public void run() { - lock.lock(); - lock.unlock(); - } - } - - private class Thread2 extends Thread { - private final Object obj; - - public Thread2(final Object obj) { - this.obj = obj; - } - - @Override - public void run() { - synchronized (obj) { - } - } - } - - private class ThreadWithCountDownLatch extends Thread { - private final CountDownLatch started; - private final CountDownLatch keepAlive; - volatile boolean finished; - - public ThreadWithCountDownLatch(final CountDownLatch started, final CountDownLatch keepAlive) { - super("ThreadWithCountDownLatch"); - this.started = started; - this.keepAlive = keepAlive; - setDaemon(true); - } - - @Override - public void run() { - started.countDown(); - try { - keepAlive.await(); - } catch (final InterruptedException e) { - // ignored - } - finished = true; - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java deleted file mode 100644 index 0d57e5ee14a..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.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.simple; - -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; - -public class SimpleLoggerTest { - - @ClassRule - public static final LogManagerLoggerContextFactoryRule rule = new LogManagerLoggerContextFactoryRule( - new SimpleLoggerContextFactory()); - - private final Logger logger = LogManager.getLogger("TestError"); - - @Test - public void testString() { - logger.error("Logging without args"); - } - - @Test - public void testMissingMessageArg() { - logger.error("Logging without args {}"); - } - - @Test - public void testEmptyObjectArray() { - logger.error(new Object[0]); - } - - /** - * Tests LOG4J2-811. - */ - @Test - public void testMessageWithEmptyObjectArray() { - logger.error("Logging with an empty Object[] {} {}", new Object[0]); - } - - /** - * Tests LOG4J2-811. - */ - @Test - public void testMessageWithShortArray() { - logger.error("Logging with a size 1 Object[] {} {}", new Object[] { "only one param" }); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java deleted file mode 100644 index e3ad5d6bc47..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.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.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; - -/** - * Tests the {@code DefaultThreadContextMap} class. - */ -public class DefaultThreadContextMapTest { - - @Test - public void testEqualsVsSameKind() { - final DefaultThreadContextMap map1 = createMap(); - final DefaultThreadContextMap map2 = createMap(); - assertEquals(map1, map1); - assertEquals(map2, map2); - assertEquals(map1, map2); - assertEquals(map2, map1); - } - - @Test - public void testHashCodeVsSameKind() { - final DefaultThreadContextMap map1 = createMap(); - final DefaultThreadContextMap map2 = createMap(); - assertEquals(map1.hashCode(), map2.hashCode()); - } - - @Test - public void testDoesNothingIfConstructedWithUseMapIsFalse() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(false); - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - map.put("key", "value"); - - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - assertNull(map.get("key")); - } - - @Test - public void testPut() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - map.put("key", "value"); - - assertFalse(map.isEmpty()); - assertTrue(map.containsKey("key")); - assertEquals("value", map.get("key")); - } - - @Test - public void testPutAll() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - final int mapSize = 10; - final Map newMap = new HashMap<>(mapSize); - for (int i = 1; i <= mapSize; i++) { - newMap.put("key" + i, "value" + i); - } - map.putAll(newMap); - assertFalse(map.isEmpty()); - for (int i = 1; i <= mapSize; i++) { - assertTrue(map.containsKey("key" + i)); - assertEquals("value" + i, map.get("key" + i)); - } - } - - /** - * Test method for - * {@link org.apache.logging.log4j.spi.DefaultThreadContextMap#remove(java.lang.String)} - * . - */ - @Test - public void testRemove() { - final DefaultThreadContextMap map = createMap(); - assertEquals("value", map.get("key")); - assertEquals("value2", map.get("key2")); - - map.remove("key"); - assertFalse(map.containsKey("key")); - assertEquals("value2", map.get("key2")); - } - - @Test - public void testClear() { - final DefaultThreadContextMap map = createMap(); - - map.clear(); - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - assertFalse(map.containsKey("key2")); - } - - /** - * @return - */ - private DefaultThreadContextMap createMap() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - map.put("key", "value"); - map.put("key2", "value2"); - assertEquals("value", map.get("key")); - assertEquals("value2", map.get("key2")); - return map; - } - - @Test - public void testGetCopyReturnsMutableMap() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - final Map copy = map.getCopy(); - assertTrue(copy.isEmpty()); - - copy.put("key", "value"); // mutable - assertEquals("value", copy.get("key")); - - // thread context map not affected - assertTrue(map.isEmpty()); - } - - @Test - public void testGetCopyReturnsMutableCopy() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - final Map copy = map.getCopy(); - assertEquals("value1", copy.get("key1")); // copy has values too - - copy.put("key", "value"); // copy is mutable - assertEquals("value", copy.get("key")); - - // thread context map not affected - assertFalse(map.containsKey("key")); - - // clearing context map does not affect copy - map.clear(); - assertTrue(map.isEmpty()); - - assertFalse(copy.isEmpty()); - } - - @Test - public void testGetImmutableMapReturnsNullIfEmpty() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - assertNull(map.getImmutableMapOrNull()); - } - - @Test(expected = UnsupportedOperationException.class) - public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - - final Map immutable = map.getImmutableMapOrNull(); - assertEquals("value1", immutable.get("key1")); // copy has values too - - // immutable - immutable.put("key", "value"); // error - } - - @Test - public void testGetImmutableMapCopyNotAffectdByContextMapChanges() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - - final Map immutable = map.getImmutableMapOrNull(); - assertEquals("value1", immutable.get("key1")); // copy has values too - - // clearing context map does not affect copy - map.clear(); - assertTrue(map.isEmpty()); - - assertFalse(immutable.isEmpty()); - } - - @Test - public void testToStringShowsMapContext() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertEquals("{}", map.toString()); - - map.put("key1", "value1"); - assertEquals("{key1=value1}", map.toString()); - - map.remove("key1"); - map.put("key2", "value2"); - assertEquals("{key2=value2}", map.toString()); - } - - @Test - public void testThreadLocalNotInheritableByDefault() { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - final ThreadLocal> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true); - 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); - } - } -} 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/spi/MutableThreadContextStackTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java deleted file mode 100644 index 7592067dbeb..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java +++ /dev/null @@ -1,367 +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.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import org.junit.Test; - -import static org.junit.Assert.*; - -public class MutableThreadContextStackTest { - - @Test - public void testEmptyIfConstructedWithEmptyList() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); - assertTrue(stack.isEmpty()); - } - - @Test - public void testConstructorCopiesListContents() { - final List initial = Arrays.asList("a", "b", "c"); - final MutableThreadContextStack stack = new MutableThreadContextStack(initial); - assertFalse(stack.isEmpty()); - assertTrue(stack.containsAll(initial)); - } - - @Test - public void testPushAndAddIncreaseStack() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); - stack.clear(); - assertTrue(stack.isEmpty()); - stack.push("msg1"); - stack.add("msg2"); - - assertEquals(2, stack.size()); - } - - @Test - public void testPeekReturnsLastAddedItem() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); - stack.clear(); - assertTrue(stack.isEmpty()); - stack.push("msg1"); - stack.add("msg2"); - - assertEquals(2, stack.size()); - assertEquals("msg2", stack.peek()); - - stack.push("msg3"); - assertEquals("msg3", stack.peek()); - } - - @Test - public void testPopRemovesLastAddedItem() { - final MutableThreadContextStack stack = createStack(); - assertEquals(3, stack.getDepth()); - - assertEquals("msg3", stack.pop()); - assertEquals(2, stack.size()); - assertEquals(2, stack.getDepth()); - - assertEquals("msg2", stack.pop()); - assertEquals(1, stack.size()); - assertEquals(1, stack.getDepth()); - - assertEquals("msg1", stack.pop()); - assertEquals(0, stack.size()); - assertEquals(0, stack.getDepth()); - } - - @Test - public void testAsList() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); - stack.clear(); - assertTrue(stack.isEmpty()); - stack.push("msg1"); - stack.add("msg2"); - stack.push("msg3"); - - assertEquals(Arrays.asList("msg1", "msg2", "msg3"), stack.asList()); - } - - @Test - public void testTrim() { - final MutableThreadContextStack stack = createStack(); - - stack.trim(1); - assertEquals(1, stack.size()); - assertEquals("msg1", stack.peek()); - } - - @Test - public void testCopy() { - final MutableThreadContextStack stack = createStack(); - - final ThreadContextStack copy = stack.copy(); - assertEquals(3, copy.size()); - assertTrue(copy.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); - - // clearing stack does not affect copy - stack.clear(); - assertTrue(stack.isEmpty()); - assertEquals(3, copy.size()); // not affected - assertTrue(copy.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); - - // adding to copy does not affect stack - copy.add("other"); - assertEquals(4, copy.size()); // not affected - assertTrue(stack.isEmpty()); - - // adding to stack does not affect copy - stack.push("newStackMsg"); - assertEquals(1, stack.size()); - assertEquals(4, copy.size()); // not affected - - // clearing copy does not affect stack - copy.clear(); - assertTrue(copy.isEmpty()); - assertEquals(1, stack.size()); - } - - @Test - public void testClear() { - final MutableThreadContextStack stack = createStack(); - - stack.clear(); - assertTrue(stack.isEmpty()); - } - - @Test - public void testEqualsVsSameKind() { - final MutableThreadContextStack stack1 = createStack(); - final MutableThreadContextStack stack2 = createStack(); - assertEquals(stack1, stack1); - assertEquals(stack2, stack2); - assertEquals(stack1, stack2); - assertEquals(stack2, stack1); - } - - @Test - public void testHashCodeVsSameKind() { - final MutableThreadContextStack stack1 = createStack(); - final MutableThreadContextStack stack2 = createStack(); - assertEquals(stack1.hashCode(), stack2.hashCode()); - } - - /** - * @return - */ - static MutableThreadContextStack createStack() { - final MutableThreadContextStack stack1 = new MutableThreadContextStack(new ArrayList()); - stack1.clear(); - assertTrue(stack1.isEmpty()); - stack1.push("msg1"); - stack1.add("msg2"); - stack1.push("msg3"); - assertEquals(3, stack1.size()); - return stack1; - } - - @Test - public void testContains() { - final MutableThreadContextStack stack = createStack(); - - assertTrue(stack.contains("msg1")); - assertTrue(stack.contains("msg2")); - assertTrue(stack.contains("msg3")); - } - - @Test - public void testIteratorReturnsInListOrderNotStackOrder() { - final MutableThreadContextStack stack = createStack(); - - final Iterator iter = stack.iterator(); - assertTrue(iter.hasNext()); - assertEquals("msg1", iter.next()); - assertTrue(iter.hasNext()); - assertEquals("msg2", iter.next()); - assertTrue(iter.hasNext()); - assertEquals("msg3", iter.next()); - assertFalse(iter.hasNext()); - } - - @Test - public void testToArray() { - final MutableThreadContextStack stack = createStack(); - - final String[] expecteds = { "msg1", "msg2", "msg3" }; - assertArrayEquals(expecteds, stack.toArray()); - } - - @Test - public void testToArrayTArray() { - final MutableThreadContextStack stack = createStack(); - - final String[] expecteds = { "msg1", "msg2", "msg3" }; - final String[] result = new String[3]; - assertArrayEquals(expecteds, stack.toArray(result)); - assertSame(result, stack.toArray(result)); - } - - @Test - public void testRemove() { - final MutableThreadContextStack stack = createStack(); - assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); - - stack.remove("msg1"); - assertEquals(2, stack.size()); - assertTrue(stack.containsAll(Arrays.asList("msg2", "msg3"))); - assertEquals("msg3", stack.peek()); - - stack.remove("msg3"); - assertEquals(1, stack.size()); - assertTrue(stack.containsAll(Arrays.asList("msg2"))); - assertEquals("msg2", stack.peek()); - } - - @Test - public void testContainsAll() { - final MutableThreadContextStack stack = createStack(); - - assertTrue(stack.containsAll(Arrays.asList("msg1", "msg2", "msg3"))); - } - - @Test - public void testAddAll() { - final MutableThreadContextStack stack = createStack(); - - stack.addAll(Arrays.asList("msg4", "msg5")); - assertEquals(5, stack.size()); - assertTrue(stack.contains("msg1")); - assertTrue(stack.contains("msg2")); - assertTrue(stack.contains("msg3")); - assertTrue(stack.contains("msg4")); - assertTrue(stack.contains("msg5")); - } - - @Test - public void testRemoveAll() { - final MutableThreadContextStack stack = createStack(); - - stack.removeAll(Arrays.asList("msg1", "msg3")); - assertEquals(1, stack.size()); - assertFalse(stack.contains("msg1")); - assertTrue(stack.contains("msg2")); - assertFalse(stack.contains("msg3")); - } - - @Test - public void testRetainAll() { - final MutableThreadContextStack stack = createStack(); - - stack.retainAll(Arrays.asList("msg1", "msg3")); - assertEquals(2, stack.size()); - assertTrue(stack.contains("msg1")); - assertFalse(stack.contains("msg2")); - assertTrue(stack.contains("msg3")); - } - - @Test - public void testToStringShowsListContents() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); - assertEquals("[]", stack.toString()); - - stack.push("msg1"); - stack.add("msg2"); - stack.push("msg3"); - assertEquals("[msg1, msg2, msg3]", stack.toString()); - - stack.retainAll(Arrays.asList("msg1", "msg3")); - assertEquals("[msg1, msg3]", stack.toString()); - } - - @Test - public void testIsFrozenIsFalseByDefault() { - assertFalse(new MutableThreadContextStack().isFrozen()); - assertFalse(createStack().isFrozen()); - } - - @Test - public void testIsFrozenIsTrueAfterCallToFreeze() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - assertFalse(stack.isFrozen()); - stack.freeze(); - assertTrue(stack.isFrozen()); - } - - @Test(expected = UnsupportedOperationException.class) - public void testAddAllOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.addAll(Arrays.asList("a", "b", "c")); - } - - @Test(expected = UnsupportedOperationException.class) - public void testAddOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.add("a"); - } - - @Test(expected = UnsupportedOperationException.class) - public void testClearOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.clear(); - } - - @Test(expected = UnsupportedOperationException.class) - public void testPopOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.pop(); - } - - @Test(expected = UnsupportedOperationException.class) - public void testPushOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.push("a"); - } - - @Test(expected = UnsupportedOperationException.class) - public void testRemoveOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.remove("a"); - } - - @Test(expected = UnsupportedOperationException.class) - public void testRemoveAllOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.removeAll(Arrays.asList("a", "b")); - } - - @Test(expected = UnsupportedOperationException.class) - public void testRetainAllOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.retainAll(Arrays.asList("a", "b")); - } - - @Test(expected = UnsupportedOperationException.class) - public void testTrimOnFrozenStackThrowsException() { - final MutableThreadContextStack stack = new MutableThreadContextStack(); - stack.freeze(); - stack.trim(3); - } -} 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/CharsetForNameMain.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java deleted file mode 100644 index b82e135ba4b..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java +++ /dev/null @@ -1,38 +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; - -public class CharsetForNameMain { - - /** - * Checks that the given Charset names can be loaded. - */ - public static void main(String[] args) { - for (String value : args) { - final String charsetName = value.trim(); - if (Charset.isSupported(charsetName)) { - Charset cs = Charset.forName(charsetName); - System.out.println(String.format("%s -> %s aliases: %s", charsetName, cs.name(), cs.aliases())); - } else { - System.err.println("Not supported:" + charsetName); - } - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassLocator.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassLocator.java deleted file mode 100644 index 9e94d0ab4ce..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassLocator.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.util; - -/** - * Created by rgoers on 3/15/17. - */ -public class ClassLocator { - - public Class locateClass() { - return StackLocatorUtil.getCallerClass(ClassLocator.class); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java deleted file mode 100644 index 0a7d48fbc8f..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.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.util; - -/** - * - */ -public class ClassNameLocator { - - public StackTraceElement locateClass() { - return StackLocatorUtil.calcLocation(ClassNameLocator.class.getName()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java deleted file mode 100644 index 930471d9cd2..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.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.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.ObjectInputStream; - -/** - * Deserializes a specified file. - * - * @see SortedArrayStringMapTest#testDeserializationOfUnknownClass() - */ -public class DeserializerHelper { - public static void main(final String... args) throws Exception { - final File file = new File(args[0]); - ObjectInputStream in = null; - try { - in = new FilteredObjectInputStream(new FileInputStream(file)); - final Object result = in.readObject(); - System.out.println(result); - } catch (final Throwable t) { - System.err.println("Could not deserialize."); - throw t; // cause non-zero exit code - } finally { - try { - in.close(); - } catch (final Throwable t) { - System.err.println("Error while closing: " + t); - } - } - } -} 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/PropertiesPropertySourceTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java deleted file mode 100644 index c1c97efaa8b..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.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.util; - -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")}, - }; - } - - @Test - public void testNormalFormFollowsCamelCaseConventions() 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/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/PropertySourceTokenizerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java deleted file mode 100644 index dd1d49cbbe5..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java +++ /dev/null @@ -1,66 +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 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")}, - {"log4j_simple", Collections.singletonList("simple")}, - {"log4j-simple", Collections.singletonList("simple")}, - {"log4j/simple", Collections.singletonList("simple")}, - {"log4j2.simple", Collections.singletonList("simple")}, - {"Log4jSimple", Collections.singletonList("simple")}, - {"LOG4J_simple", Collections.singletonList("simple")}, - {"org.apache.logging.log4j.simple", Collections.singletonList("simple")}, - {"log4j.simpleProperty", Arrays.asList("simple", "property")}, - {"log4j.simple_property", Arrays.asList("simple", "property")}, - {"LOG4J_simple_property", Arrays.asList("simple", "property")}, - {"LOG4J_SIMPLE_PROPERTY", Arrays.asList("simple", "property")}, - {"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")}, - }; - } - - @Test - public void testTokenize() throws Exception { - 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/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java deleted file mode 100644 index ce4236afb10..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.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 java.io.File; -import java.net.URL; -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 static org.junit.Assert.assertTrue; - -public class ProviderUtilTest { - - @Test - public void complexTest() throws Exception { - File file = new File("target/classes"); - ClassLoader classLoader = new URLClassLoader(new URL[] {file.toURI().toURL()}); - Worker worker = new Worker(); - worker.setContextClassLoader(classLoader); - worker.start(); - worker.join(); - assertTrue("Incorrect LoggerContext", worker.context instanceof TestLoggerContext); - } - - private class Worker extends Thread { - LoggerContext context = null; - - @Override - public void run() { - context = LogManager.getContext(false); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java deleted file mode 100644 index 3d9ce3e8ae4..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java +++ /dev/null @@ -1,1118 +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.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.charset.Charset; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the SortedArrayStringMap class. - */ -public class SortedArrayStringMapTest { - - @Test(expected = IllegalArgumentException.class) - public void testConstructorDisallowsNegativeCapacity() throws Exception { - new SortedArrayStringMap(-1); - } - - public void testConstructorAllowsZeroCapacity() throws Exception { - SortedArrayStringMap sortedArrayStringMap = new SortedArrayStringMap(0); - assertEquals(0, sortedArrayStringMap.size()); - } - - @Test - public void testConstructorIgnoresNull() throws Exception { - assertEquals(0, new SortedArrayStringMap((SortedArrayStringMap) null).size()); - } - - @Test - public void testToString() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - assertEquals("{3=3value, B=Bvalue, a=avalue}", original.toString()); - } - - @Test - public void testSerialization() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - final byte[] binary = serialize(original); - final SortedArrayStringMap copy = deserialize(binary); - assertEquals(original, copy); - } - - @Test - public void testSerializationOfNonSerializableValue() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("unserializable", new Object()); - - final byte[] binary = serialize(original); - final SortedArrayStringMap copy = deserialize(binary); - - final SortedArrayStringMap expected = new SortedArrayStringMap(); - expected.putValue("a", "avalue"); - expected.putValue("B", "Bvalue"); - expected.putValue("unserializable", null); - assertEquals(expected, copy); - } - - @Test - public void testDeserializationOfUnknownClass() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("serializableButNotInClasspathOfDeserializer", new org.junit.runner.Result()); - original.putValue("zz", "last"); - - final File file = new File("target/SortedArrayStringMap.ser"); - try (FileOutputStream fout = new FileOutputStream(file, false)) { - fout.write(serialize(original)); - fout.flush(); - } - final String classpath = createClassPath(SortedArrayStringMap.class, DeserializerHelper.class); - final Process process = new ProcessBuilder("java", "-cp", classpath, - DeserializerHelper.class.getName(), file.getPath()).start(); - final BufferedReader in = new BufferedReader(new InputStreamReader(process.getErrorStream())); - final int exitValue = process.waitFor(); - - file.delete(); - if (exitValue != 0) { - final StringBuilder sb = new StringBuilder(); - sb.append("DeserializerHelper exited with error code ").append(exitValue); - sb.append(". Classpath='").append(classpath); - sb.append("'. Process output: "); - int c = -1; - while ((c = in.read()) != -1) { - sb.append((char) c); - } - fail(sb.toString()); - } - } - - private String createClassPath(final Class... classes) throws Exception { - final StringBuilder result = new StringBuilder(); - for (final Class cls : classes) { - if (result.length() > 0) { - result.append(File.pathSeparator); - } - result.append(createClassPath(cls)); - } - return result.toString(); - } - - private String createClassPath(final Class cls) throws Exception { - final String resource = "/" + cls.getName().replace('.', '/') + ".class"; - final URL url = cls.getResource(resource); - String location = url.toString(); - if (location.startsWith("jar:")) { - location = location.substring("jar:".length(), location.indexOf('!')); - } - if (location.startsWith("file:/")) { - location = location.substring("file:/".length()); - } - if (location.endsWith(resource)) { - location = location.substring(0, location.length() - resource.length()); - } - if (!new File(location).exists()) { - location = File.separator + location; - } - location = URLDecoder.decode(location, Charset.defaultCharset().name()); // replace %20 with ' ' etc - return location.isEmpty() ? "." : location; - } - - private byte[] serialize(final SortedArrayStringMap data) throws IOException { - final ByteArrayOutputStream arr = new ByteArrayOutputStream(); - final ObjectOutputStream out = new ObjectOutputStream(arr); - out.writeObject(data); - return arr.toByteArray(); - } - - 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; - } - } - - @Test - public void testPutAll() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putAll(original); - assertEquals(original, other); - - other.putValue("3", "otherValue"); - assertNotEquals(original, other); - - other.putValue("3", null); - assertNotEquals(original, other); - - other.putValue("3", "3value"); - assertEquals(original, other); - } - - @Test - public void testPutAll_overwritesSameKeys2() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - original.putValue("c", "cORIG"); - original.putValue("d", "dORIG"); - original.putValue("e", "eORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "11"); - other.putValue("2", "22"); - other.putValue("a", "aa"); - other.putValue("c", "cc"); - original.putAll(other); - - assertEquals("size after put other", 7, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("cc", original.getValue("c")); - assertEquals("dORIG", original.getValue("d")); - assertEquals("eORIG", original.getValue("e")); - assertEquals("11", original.getValue("1")); - assertEquals("22", original.getValue("2")); - } - - @Test - public void testPutAll_nullKeyInLargeOriginal() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue(null, "nullORIG"); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - original.putValue("c", "cORIG"); - original.putValue("d", "dORIG"); - original.putValue("e", "eORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "11"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 7, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("cORIG", original.getValue("c")); - assertEquals("dORIG", original.getValue("d")); - assertEquals("eORIG", original.getValue("e")); - assertEquals("11", original.getValue("1")); - assertEquals("nullORIG", original.getValue(null)); - } - - @Test - public void testPutAll_nullKeyInSmallOriginal() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue(null, "nullORIG"); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "11"); - other.putValue("2", "22"); - other.putValue("3", "33"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 6, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("11", original.getValue("1")); - assertEquals("22", original.getValue("2")); - assertEquals("33", original.getValue("3")); - assertEquals("nullORIG", original.getValue(null)); - } - - @Test - public void testPutAll_nullKeyInSmallAdditional() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - original.putValue("c", "cORIG"); - original.putValue("d", "dORIG"); - original.putValue("e", "eORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue(null, "nullNEW"); - other.putValue("1", "11"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 7, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("cORIG", original.getValue("c")); - assertEquals("dORIG", original.getValue("d")); - assertEquals("eORIG", original.getValue("e")); - assertEquals("11", original.getValue("1")); - assertEquals("nullNEW", original.getValue(null)); - } - - @Test - public void testPutAll_nullKeyInLargeAdditional() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue(null, "nullNEW"); - other.putValue("1", "11"); - other.putValue("2", "22"); - other.putValue("3", "33"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 6, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("11", original.getValue("1")); - assertEquals("22", original.getValue("2")); - assertEquals("33", original.getValue("3")); - assertEquals("nullNEW", original.getValue(null)); - } - - @Test - public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue(null, "nullORIG"); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - original.putValue("c", "cORIG"); - original.putValue("d", "dORIG"); - original.putValue("e", "eORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue(null, "nullNEW"); - other.putValue("1", "11"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 7, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("cORIG", original.getValue("c")); - assertEquals("dORIG", original.getValue("d")); - assertEquals("eORIG", original.getValue("e")); - assertEquals("11", original.getValue("1")); - assertEquals("nullNEW", original.getValue(null)); - } - - @Test - public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue(null, "nullORIG"); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue(null, "nullNEW"); - other.putValue("1", "11"); - other.putValue("2", "22"); - other.putValue("3", "33"); - other.putValue("a", "aa"); - original.putAll(other); - - assertEquals("size after put other", 6, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("11", original.getValue("1")); - assertEquals("22", original.getValue("2")); - assertEquals("33", original.getValue("3")); - assertEquals("nullNEW", original.getValue(null)); - } - - @Test - public void testPutAll_overwritesSameKeys1() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aORIG"); - original.putValue("b", "bORIG"); - original.putValue("c", "cORIG"); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "11"); - other.putValue("2", "22"); - other.putValue("a", "aa"); - other.putValue("c", "cc"); - original.putAll(other); - - assertEquals("size after put other", 5, original.size()); - assertEquals("aa", original.getValue("a")); - assertEquals("bORIG", original.getValue("b")); - assertEquals("cc", original.getValue("c")); - assertEquals("11", original.getValue("1")); - assertEquals("22", original.getValue("2")); - } - - @Test - public void testEquals() { - final SortedArrayStringMap original = new 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); - - other.putValue("B", "Bvalue"); - assertNotEquals(original, other); - - other.putValue("3", "3value"); - assertEquals(original, other); - - other.putValue("3", "otherValue"); - assertNotEquals(original, other); - - other.putValue("3", null); - assertNotEquals(original, other); - - other.putValue("3", "3value"); - assertEquals(original, other); - } - - @Test - public void testToMap() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - final Map expected = new HashMap<>(); - expected.put("a", "avalue"); - expected.put("B", "Bvalue"); - expected.put("3", "3value"); - - assertEquals(expected, original.toMap()); - - try { - original.toMap().put("abc", "xyz"); - } catch (final UnsupportedOperationException ex) { - fail("Expected map to be mutable, but " + ex); - } - } - - @Test - public void testPutAll_KeepsExistingValues() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aaa"); - original.putValue("b", "bbb"); - original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); - - // add empty context data - original.putAll(new SortedArrayStringMap()); - assertEquals("size after put empty", 3, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "111"); - other.putValue("2", "222"); - other.putValue("3", "333"); - original.putAll(other); - - assertEquals("size after put other", 6, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - assertEquals("111", original.getValue("1")); - assertEquals("222", original.getValue("2")); - assertEquals("333", original.getValue("3")); - } - - @Test - public void testPutAll_sizePowerOfTwo() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aaa"); - original.putValue("b", "bbb"); - original.putValue("c", "ccc"); - original.putValue("d", "ddd"); - assertEquals("size", 4, original.size()); - - // add empty context data - original.putAll(new SortedArrayStringMap()); - assertEquals("size after put empty", 4, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - assertEquals("ddd", original.getValue("d")); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - other.putValue("1", "111"); - other.putValue("2", "222"); - other.putValue("3", "333"); - other.putValue("4", "444"); - original.putAll(other); - - assertEquals("size after put other", 8, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - assertEquals("ddd", original.getValue("d")); - assertEquals("111", original.getValue("1")); - assertEquals("222", original.getValue("2")); - assertEquals("333", original.getValue("3")); - assertEquals("444", original.getValue("4")); - } - - @Test - public void testPutAll_largeAddition() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue(null, "nullVal"); - original.putValue("a", "aaa"); - original.putValue("b", "bbb"); - original.putValue("c", "ccc"); - original.putValue("d", "ddd"); - assertEquals("size", 5, original.size()); - - final SortedArrayStringMap other = new SortedArrayStringMap(); - for (int i = 0 ; i < 500; i++) { - other.putValue(String.valueOf(i), String.valueOf(i)); - } - other.putValue(null, "otherVal"); - original.putAll(other); - - assertEquals("size after put other", 505, original.size()); - assertEquals("otherVal", original.getValue(null)); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - assertEquals("ddd", original.getValue("d")); - for (int i = 0 ; i < 500; i++) { - assertEquals(String.valueOf(i), original.getValue(String.valueOf(i))); - } - } - - @Test - public void testPutAllSelfDoesNotModify() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aaa"); - original.putValue("b", "bbb"); - original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); - - // putAll with self - original.putAll(original); - assertEquals("size after put empty", 3, original.size()); - assertEquals("aaa", original.getValue("a")); - assertEquals("bbb", original.getValue("b")); - assertEquals("ccc", original.getValue("c")); - } - - @Test(expected = ConcurrentModificationException.class) - 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"); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - 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"); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - 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"); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - 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(); - } - }); - } - - @Test(expected = ConcurrentModificationException.class) - 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); - } - - @Test(expected = ConcurrentModificationException.class) - 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); - } - - @Test(expected = ConcurrentModificationException.class) - 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); - } - - @Test(expected = ConcurrentModificationException.class) - 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); - } - - @Test - public void testInitiallyNotFrozen() { - assertFalse(new SortedArrayStringMap().isFrozen()); - } - - @Test - public void testIsFrozenAfterCallingFreeze() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - assertFalse("before freeze", original.isFrozen()); - original.freeze(); - assertTrue("after freeze", original.isFrozen()); - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsPutValue() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.freeze(); - original.putValue("a", "aaa"); - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsRemove() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("b", "bbb"); - original.freeze(); - original.remove("b"); // existing key: modifies the collection - } - - @Test - public void testFreezeAllowsRemoveOfNonExistingKey() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("b", "bbb"); - original.freeze(); - original.remove("a"); // no actual modification - } - - @Test - public void testFreezeAllowsRemoveIfEmpty() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.freeze(); - original.remove("a"); // no exception - } - - @Test(expected = UnsupportedOperationException.class) - public void testFreezeProhibitsClear() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "aaa"); - original.freeze(); - original.clear(); - } - - @Test - public void testFreezeAllowsClearIfEmpty() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.freeze(); - original.clear(); - } - - @Test - public void testPutInsertsInAlphabeticOrder() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - - assertEquals("avalue", original.getValue("a")); - assertEquals("avalue", original.getValueAt(2)); - - assertEquals("Bvalue", original.getValue("B")); - assertEquals("Bvalue", original.getValueAt(1)); - - assertEquals("3value", original.getValue("3")); - assertEquals("3value", original.getValueAt(0)); - - assertEquals("cvalue", original.getValue("c")); - assertEquals("cvalue", original.getValueAt(3)); - - assertEquals("dvalue", original.getValue("d")); - assertEquals("dvalue", original.getValueAt(4)); - } - - @Test - public void testPutValueInsertsInAlphabeticOrder() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - - assertEquals("avalue", original.getValue("a")); - assertEquals("avalue", original.getValueAt(2)); - - assertEquals("Bvalue", original.getValue("B")); - assertEquals("Bvalue", original.getValueAt(1)); - - assertEquals("3value", original.getValue("3")); - assertEquals("3value", original.getValueAt(0)); - - assertEquals("cvalue", original.getValue("c")); - assertEquals("cvalue", original.getValueAt(3)); - - assertEquals("dvalue", original.getValue("d")); - assertEquals("dvalue", original.getValueAt(4)); - } - - @Test - public void testNullKeysAllowed() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - assertEquals(5, original.size()); - assertEquals("{3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); - - original.putValue(null, "nullvalue"); - assertEquals(6, original.size()); - assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); - - original.putValue(null, "otherNullvalue"); - assertEquals("{null=otherNullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); - assertEquals(6, original.size()); - - original.putValue(null, "nullvalue"); - assertEquals(6, original.size()); - assertEquals("{null=nullvalue, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); - - original.putValue(null, "abc"); - assertEquals(6, original.size()); - assertEquals("{null=abc, 3=3value, B=Bvalue, a=avalue, c=cvalue, d=dvalue}", original.toString()); - } - - @Test - public void testNullKeysCopiedToAsMap() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - assertEquals(5, original.size()); - - final HashMap expected = new HashMap<>(); - expected.put("a", "avalue"); - expected.put("B", "Bvalue"); - expected.put("3", "3value"); - expected.put("c", "cvalue"); - expected.put("d", "dvalue"); - assertEquals("initial", expected, original.toMap()); - - original.putValue(null, "nullvalue"); - expected.put(null, "nullvalue"); - assertEquals(6, original.size()); - assertEquals("with null key", expected, original.toMap()); - - original.putValue(null, "otherNullvalue"); - expected.put(null, "otherNullvalue"); - assertEquals(6, original.size()); - assertEquals("with null key value2", expected, original.toMap()); - - original.putValue(null, "nullvalue"); - expected.put(null, "nullvalue"); - assertEquals(6, original.size()); - assertEquals("with null key value1 again", expected, original.toMap()); - - original.putValue(null, "abc"); - expected.put(null, "abc"); - assertEquals(6, original.size()); - assertEquals("with null key value3", expected, original.toMap()); - } - - @Test - public void testRemove() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - assertEquals(1, original.size()); - assertEquals("avalue", original.getValue("a")); - - original.remove("a"); - assertEquals(0, original.size()); - assertNull("no a val", original.getValue("a")); - - original.remove("B"); - assertEquals(0, original.size()); - assertNull("no B val", original.getValue("B")); - } - - @Test - public void testRemoveNullsOutRemovedSlot() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("b", "bvalue"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); - original.remove("a"); - original.remove("b"); - original.remove("c"); - original.remove("d"); - assertNull(original.getValueAt(0)); - - // ensure slots in the values array are nulled out - 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]); - } - } - - @Test - public void testRemoveWhenFull() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("b", "bvalue"); - original.putValue("c", "cvalue"); - original.putValue("d", "dvalue"); // default capacity = 4 - original.remove("d"); - } - - @Test - public void testNullValuesArePreserved() { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - assertEquals(1, original.size()); - assertEquals("avalue", original.getValue("a")); - - original.putValue("a", null); - assertEquals(1, original.size()); - assertNull("no a val", original.getValue("a")); - - original.putValue("B", null); - assertEquals(2, original.size()); - assertNull("no B val", original.getValue("B")); - } - - @Test - public void testGet() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - assertEquals("avalue", original.getValue("a")); - assertEquals("Bvalue", original.getValue("B")); - assertEquals("3value", original.getValue("3")); - - original.putValue("0", "0value"); - assertEquals("0value", original.getValue("0")); - assertEquals("3value", original.getValue("3")); - assertEquals("Bvalue", original.getValue("B")); - assertEquals("avalue", original.getValue("a")); - } - - @Test - public void testGetValue_GetValueAt() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - assertEquals("avalue", original.getValue("a")); - assertEquals("avalue", original.getValueAt(2)); - - assertEquals("Bvalue", original.getValue("B")); - assertEquals("Bvalue", original.getValueAt(1)); - - assertEquals("3value", original.getValue("3")); - assertEquals("3value", original.getValueAt(0)); - - original.putValue("0", "0value"); - assertEquals("0value", original.getValue("0")); - assertEquals("0value", original.getValueAt(0)); - assertEquals("3value", original.getValue("3")); - assertEquals("3value", original.getValueAt(1)); - assertEquals("Bvalue", original.getValue("B")); - assertEquals("Bvalue", original.getValueAt(2)); - assertEquals("avalue", original.getValue("a")); - assertEquals("avalue", original.getValueAt(3)); - } - - @Test - public void testClear() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - assertEquals(3, original.size()); - - original.clear(); - assertEquals(0, original.size()); - - // ensure slots in the values array are nulled out - 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]); - } - } - - @Test - public void testIndexOfKey() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - assertEquals(0, original.indexOfKey("a")); - - original.putValue("B", "Bvalue"); - assertEquals(1, original.indexOfKey("a")); - assertEquals(0, original.indexOfKey("B")); - - original.putValue("3", "3value"); - assertEquals(2, original.indexOfKey("a")); - assertEquals(1, original.indexOfKey("B")); - assertEquals(0, original.indexOfKey("3")); - - original.putValue("A", "AAA"); - assertEquals(3, original.indexOfKey("a")); - assertEquals(2, original.indexOfKey("B")); - assertEquals(1, original.indexOfKey("A")); - assertEquals(0, original.indexOfKey("3")); - - original.putValue("C", "CCC"); - assertEquals(4, original.indexOfKey("a")); - assertEquals(3, original.indexOfKey("C")); - assertEquals(2, original.indexOfKey("B")); - assertEquals(1, original.indexOfKey("A")); - assertEquals(0, original.indexOfKey("3")); - - original.putValue("2", "222"); - assertEquals(5, original.indexOfKey("a")); - assertEquals(4, original.indexOfKey("C")); - assertEquals(3, original.indexOfKey("B")); - assertEquals(2, original.indexOfKey("A")); - assertEquals(1, original.indexOfKey("3")); - assertEquals(0, original.indexOfKey("2")); - } - - @Test - public void testContainsKey() throws Exception { - 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")); - - original.putValue("a", "avalue"); - assertTrue("a", original.containsKey("a")); - assertFalse("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("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")); - - original.putValue("3", "3value"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertTrue("3", original.containsKey("3")); - assertFalse("A", original.containsKey("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")); - } - - @Test - public void testGetValueAt() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - assertEquals("a", original.getKeyAt(0)); - assertEquals("avalue", original.getValueAt(0)); - - original.putValue("B", "Bvalue"); - assertEquals("B", original.getKeyAt(0)); - assertEquals("Bvalue", original.getValueAt(0)); - assertEquals("a", original.getKeyAt(1)); - assertEquals("avalue", original.getValueAt(1)); - - original.putValue("3", "3value"); - assertEquals("3", original.getKeyAt(0)); - assertEquals("3value", original.getValueAt(0)); - assertEquals("B", original.getKeyAt(1)); - assertEquals("Bvalue", original.getValueAt(1)); - assertEquals("a", original.getKeyAt(2)); - assertEquals("avalue", original.getValueAt(2)); - } - - @Test - public void testSizeAndIsEmpty() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - assertEquals(0, original.size()); - assertTrue("initial", original.isEmpty()); - - original.putValue("a", "avalue"); - assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); - - original.putValue("B", "Bvalue"); - assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); - - original.putValue("3", "3value"); - assertEquals(3, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); - - original.remove("B"); - assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); - - original.remove("3"); - assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); - - original.remove("a"); - assertEquals(0, original.size()); - assertTrue("size=" + original.size(), original.isEmpty()); - } - - @Test - public void testForEachBiConsumer() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - original.forEach(new BiConsumer() { - 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)); - count++; - assertTrue("count should not exceed size but was " + count, count <= original.size()); - } - }); - } - - 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()); - } - }; - - @Test - public void testForEachTriConsumer() throws Exception { - final SortedArrayStringMap original = new SortedArrayStringMap(); - original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); - original.putValue("3", "3value"); - - final State state = new State(); - state.data = original; - original.forEach(COUNTER, state); - assertEquals(state.count, original.size()); - } -} \ 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/SystemPropertiesMain.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java deleted file mode 100644 index 19b49810322..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.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.util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; - -/** - * Prints system properties to the console. - */ -public class SystemPropertiesMain { - - /** - * Prints system properties to the console. - * - * @param args - * unused - */ - public static void main(String[] args) { - @SuppressWarnings("unchecked") - Enumeration keyEnum = (Enumeration) System.getProperties().propertyNames(); - List list = new ArrayList<>(); - while (keyEnum.hasMoreElements()) { - list.add(keyEnum.nextElement()); - } - Collections.sort(list); - for (String key : list) { - System.out.println(key + " = " + System.getProperty(key)); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java deleted file mode 100644 index 2e8cb774054..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.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 org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the Unbox class. - */ -public class Unbox1Test { - @BeforeClass - public static void beforeClass() { - System.clearProperty("log4j.unbox.ringbuffer.size"); - } - - @Test - public void testBoxClaimsItHas32Slots() throws Exception { - assertEquals(32, Unbox.getRingbufferSize()); - } - - @Test - public void testBoxHas32Slots() throws Exception { - final int MAX = 32; - 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]); - } - } - } - - @Test - public void testBoxBoolean() throws Exception { - assertEquals("true", Unbox.box(true).toString()); - assertEquals("false", Unbox.box(false).toString()); - } - - @Test - public void testBoxByte() throws Exception { - assertEquals("0", Unbox.box((byte) 0).toString()); - assertEquals("1", Unbox.box((byte) 1).toString()); - assertEquals("127", Unbox.box((byte) 127).toString()); - assertEquals("-1", Unbox.box((byte) -1).toString()); - assertEquals("-128", Unbox.box((byte) -128).toString()); - } - - @Test - public void testBoxChar() throws Exception { - assertEquals("a", Unbox.box('a').toString()); - assertEquals("b", Unbox.box('b').toString()); - assertEquals("字", Unbox.box('字').toString()); - } - - @Test - public void testBoxDouble() throws Exception { - 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 { - 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 { - assertEquals("0", Unbox.box(0).toString()); - assertEquals("1", Unbox.box(1).toString()); - assertEquals("127", Unbox.box(127).toString()); - assertEquals("-1", Unbox.box(-1).toString()); - assertEquals("-128", Unbox.box(-128).toString()); - assertEquals(new Integer(Integer.MAX_VALUE).toString(), Unbox.box(Integer.MAX_VALUE).toString()); - assertEquals(new Integer(Integer.MIN_VALUE).toString(), Unbox.box(Integer.MIN_VALUE).toString()); - } - - @Test - public void testBoxLong() throws Exception { - assertEquals("0", Unbox.box(0L).toString()); - assertEquals("1", Unbox.box(1L).toString()); - assertEquals("127", Unbox.box(127L).toString()); - assertEquals("-1", Unbox.box(-1L).toString()); - assertEquals("-128", Unbox.box(-128L).toString()); - assertEquals(new Long(Long.MAX_VALUE).toString(), Unbox.box(Long.MAX_VALUE).toString()); - assertEquals(new Long(Long.MIN_VALUE).toString(), Unbox.box(Long.MIN_VALUE).toString()); - } - - @Test - public void testBoxShort() throws Exception { - assertEquals("0", Unbox.box((short) 0).toString()); - assertEquals("1", Unbox.box((short) 1).toString()); - assertEquals("127", Unbox.box((short) 127).toString()); - assertEquals("-1", Unbox.box((short) -1).toString()); - assertEquals("-128", Unbox.box((short) -128).toString()); - assertEquals(new Short(Short.MAX_VALUE).toString(), Unbox.box(Short.MAX_VALUE).toString()); - assertEquals(new Short(Short.MIN_VALUE).toString(), Unbox.box(Short.MIN_VALUE).toString()); - } - - @Test - 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); - } - }; - t1.start(); - t1.join(); - final Thread t2 = new Thread() { - @Override - public void run() { - 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]); - } - } - } - - private void populate(final int start, final StringBuilder[] probe) { - for (int i = start; i <= start + 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); - } - } -} \ No newline at end of file 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-api/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider deleted file mode 100644 index 5ae649a3f92..00000000000 --- a/log4j-api/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider +++ /dev/null @@ -1 +0,0 @@ -org.apache.logging.log4j.TestProvider \ No newline at end of file diff --git a/log4j-api/src/test/resources/PropertiesUtilTest.properties b/log4j-api/src/test/resources/PropertiesUtilTest.properties deleted file mode 100644 index 46e67d58be5..00000000000 --- a/log4j-api/src/test/resources/PropertiesUtilTest.properties +++ /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. -# - -a.1 = 1 -a.2 = 2 -a.3 = 3 -b.1 = 1 -b.2 = 2 -b.3 = 3 -c.1.1 = 1 -c.1.2 = 2 -c.1.3 = 3 -dd.1 = 1 -dd.2 = 2 -dd.3 = 3 diff --git a/log4j-appserver/pom.xml b/log4j-appserver/pom.xml index bd1351fd941..f139611ad74 100644 --- a/log4j-appserver/pom.xml +++ b/log4j-appserver/pom.xml @@ -3,11 +3,11 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed 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 ~ - ~ 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, @@ -15,14 +15,16 @@ ~ 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 + log4j + ${revision} + ../log4j-parent - 4.0.0 log4j-appserver jar @@ -30,49 +32,34 @@ Provide Log4j as the logging implementation for application servers - ${basedir}/.. - Web Documentation - /log4j-appserver - 8.5.20 - 8.2.0.v20160908 - org.apache.logging.log4j.appserver + + + + + org.apache.juli.logging;version="[8.0,12)";resolution:=optional, + + org.eclipse.jetty.util.log;version="[7.6,10)";resolution:=optional + + + + org.apache.tomcat.juli;transitive=false, + org.eclipse.jetty.util;transitive=false + + + + 9.4.57.v20241219 + 10.0.27 - - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-core - - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - org.apache.tomcat - tomcat-catalina - ${tomcat.version} - provided - - - org.apache.tomcat - tomcat-annotations-api - - - org.apache.tomcat - tomcat-jsp-api - - - org.apache.tomcat - tomcat-el-api - - - + org.eclipse.jetty jetty-util @@ -80,141 +67,17 @@ provided - - - org.apache.logging.log4j - log4j-core - test-jar - test - - junit - junit - test - - - org.springframework - spring-test - test + org.apache.tomcat + tomcat-juli + ${tomcat-juli.version} + provided + - org.mockito - mockito-core - test + org.apache.logging.log4j + log4j-api - - - - - org.apache.felix - maven-bundle-plugin - - - - org.apache.logging.log4j.core - javax.servlet;version="[2.5,4)",* - org.apache.logging.log4j.web - - - - - - - - - org.apache.maven.plugins - maven-changes-plugin - ${changes.plugin.version} - - - - changes-report - - - - - %URL%/show_bug.cgi?id=%ISSUE% - true - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${checkstyle.plugin.version} - - - ${log4jParentDir}/checkstyle.xml - ${log4jParentDir}/checkstyle-suppressions.xml - false - basedir=${basedir} - licensedir=${log4jParentDir}/checkstyle-header.txt - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc.plugin.version} - - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
- 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.

]]>
- - false - true - - http://docs.oracle.com/javaee/6/api/ - -
- - - non-aggregate - - javadoc - - - -
- - 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 - - jxr - - - - aggregate - - aggregate - - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${pmd.plugin.version} - - ${maven.compiler.target} - - -
-
+
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..70b93443287 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 @@ -1,22 +1,22 @@ /* * 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.logging.log4j.appserver.jetty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.ExtendedLogger; @@ -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. *

@@ -52,6 +52,7 @@ public class Log4j2Logger extends AbstractLogger { */ private static class PrivateManager extends LogManager { + @SuppressFBWarnings("HSM_HIDING_METHOD") public static LoggerContext getContext() { final ClassLoader cl = AbstractLogger.class.getClassLoader(); return getContext(PARENT_FQCN, cl, false); @@ -72,7 +73,6 @@ public Log4j2Logger() { } public Log4j2Logger(final String name) { - super(); this.name = name; this.logger = PrivateManager.getLogger(name); } @@ -216,5 +216,4 @@ public void warn(final String msg, final Throwable thrown) { public void warn(final Throwable thrown) { logger.logIfEnabled(FQCN, Level.WARN, null, (Object) null, thrown); } - } diff --git a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/package-info.java b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/package-info.java index d51065b3253..41b7bce8119 100644 --- a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/package-info.java +++ b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/package-info.java @@ -17,4 +17,11 @@ /** * Log4j integration with Eclipse Jetty. */ +@Open +@Export +@Version("2.20.1") package org.apache.logging.log4j.appserver.jetty; + +import aQute.bnd.annotation.jpms.Open; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/TomcatLogger.java b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/TomcatLogger.java index a91194adcb7..11817c66189 100644 --- a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/TomcatLogger.java +++ b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/TomcatLogger.java @@ -1,25 +1,27 @@ /* * 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.logging.log4j.appserver.tomcat; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; - import org.apache.juli.logging.Log; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -41,13 +43,13 @@ * * @since 2.10.0 */ +@ServiceProvider(value = Log.class, resolution = Resolution.OPTIONAL) public class TomcatLogger implements Log { private static final long serialVersionUID = 1L; private static final String FQCN = TomcatLogger.class.getName(); private static final String[] FILE_NAMES = { - "log4j2-tomcat.xml", "log4j2-tomcat.json", "log4j2-tomcat.yaml", "log4j2-tomcat.yml", - "log4j2-tomcat.properties" + "log4j2-tomcat.xml", "log4j2-tomcat.json", "log4j2-tomcat.yaml", "log4j2-tomcat.yml", "log4j2-tomcat.properties" }; private final ExtendedLogger logger; @@ -162,6 +164,7 @@ public void fatal(final Object o, final Throwable throwable) { */ private static class PrivateManager extends LogManager { + @SuppressFBWarnings("HSM_HIDING_METHOD") public static LoggerContext getContext() { final ClassLoader cl = TomcatLogger.class.getClassLoader(); URI uri = null; diff --git a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/package-info.java b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/package-info.java index ca9ff7c52a5..d8d724f4ebf 100644 --- a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/package-info.java +++ b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/tomcat/package-info.java @@ -17,4 +17,9 @@ /** * Log4j integration with Apache Tomcat 8.5 or greater. */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.appserver.tomcat; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-appserver/src/main/resources/META-INF/services/org.apache.juli.logging.Log b/log4j-appserver/src/main/resources/META-INF/services/org.apache.juli.logging.Log deleted file mode 100644 index 50771871ac8..00000000000 --- a/log4j-appserver/src/main/resources/META-INF/services/org.apache.juli.logging.Log +++ /dev/null @@ -1,17 +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. -# -org.apache.logging.log4j.appserver.tomcat.TomcatLogger \ No newline at end of file diff --git a/log4j-appserver/src/site/markdown/index.md.vm b/log4j-appserver/src/site/markdown/index.md.vm deleted file mode 100644 index 7128bc22ed0..00000000000 --- a/log4j-appserver/src/site/markdown/index.md.vm +++ /dev/null @@ -1,59 +0,0 @@ - - - - -#set($h1 = '#') -#set($h2 = '##') -#set($h3 = '###') -#set($h4 = '####') - -$h1 Application Server Integration - -The Application Server module provides support for integrating Log4j into various Java Application Servers. - -$h2 Apache Tomcat - -Log4j may be used as the logging framework for Apache Tomcat. This support is implemented automatically by including -the log4j-api, log4j-core, and log4j-appserver jars in the boot classpath. A file named log4j2-tomcat.xml, -log4j2-tomcat.json, log4j2-tomcat.yaml, log4j2-tomcat.yml, or log4j2-tomcat.properties must also be placed -in the boot classpath. This is most easily done by: - -1. Creating a set of directories in catalina home named log4j2/lib and log4j2/conf. -2. Placing log4j2-api-${Log4jReleaseVersion}.jar, log4j2-core-${Log4jReleaseVersion}.jar, and -log4j2-appserver-${Log4jReleaseVersion}.jar in the log4j2/lib directory. -3. Creating a file named log4j2-tomcat.xml, log4j2-tomcat.json, log4j2-tomcat.yaml, log4j2-tomcat.yml, or -log4j2-tomcat.properties in the log4j2/conf directory. -4. Create or modify setenv.sh in the tomcat bin directory to include -```CLASSPATH=$CATALINA_HOME/log4j2/lib/*:$CATALINA_HOME/log4j2/conf``` - -$h3 Requirements - -Requires Tomcat 8.5 or later. - -$h2 Eclipse Jetty - -Log4j may be used as the logging framework for Eclipse Jetty. - -To direct Jetty to use this class, set the system property `org.eclipse.jetty.util.log.class` to `org.apache.logging.log4j.appserver.jetty.Log4j2Logger`. - -From the command line with: -```-Dorg.eclipse.jetty.util.log.class = org.apache.logging.log4j.appserver.jetty.Log4j2Logger``` - -Programmatically with: -```System.setProperty("org.eclipse.jetty.util.log.class", "org.apache.logging.log4j.appserver.jetty.Log4j2Logger");``` - \ No newline at end of file diff --git a/log4j-appserver/src/site/site.xml b/log4j-appserver/src/site/site.xml deleted file mode 100644 index 71457127ab4..00000000000 --- a/log4j-appserver/src/site/site.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/log4j-bom/pom.xml b/log4j-bom/pom.xml deleted file mode 100644 index 281572a9acf..00000000000 --- a/log4j-bom/pom.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - org.apache.logging - logging-parent - 1 - - 4.0.0 - Apache Log4j BOM - Apache Log4j Bill of Materials - org.apache.logging.log4j - log4j-bom - 2.10.1-SNAPSHOT - pom - - - - - org.apache.logging.log4j - log4j-api - ${project.version} - - - - org.apache.logging.log4j - log4j-core - ${project.version} - - - - org.apache.logging.log4j - log4j-1.2-api - ${project.version} - - - - org.apache.logging.log4j - log4j-jcl - ${project.version} - - - - org.apache.logging.log4j - log4j-flume-ng - ${project.version} - - - - org.apache.logging.log4j - log4j-taglib - ${project.version} - - - - org.apache.logging.log4j - log4j-jmx-gui - ${project.version} - - - - org.apache.logging.log4j - log4j-slf4j-impl - ${project.version} - - - - org.apache.logging.log4j - log4j-web - ${project.version} - - - - org.apache.logging.log4j - log4j-couchdb - ${project.version} - - - - org.apache.logging.log4j - log4j-mongodb2 - ${project.version} - - - - org.apache.logging.log4j - log4j-mongodb3 - ${project.version} - - - - org.apache.logging.log4j - log4j-cassandra - ${project.version} - - - - org.apache.logging.log4j - log4j-jpa - ${project.version} - - - - org.apache.logging.log4j - log4j-iostreams - ${project.version} - - - - org.apache.logging.log4j - log4j-jul - ${project.version} - - - - org.apache.logging.log4j - log4j-liquibase - ${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/.log4j-plugin-processing-activator b/log4j-cassandra/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-cassandra/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-cassandra/pom.xml b/log4j-cassandra/pom.xml index e3c8cb3d8ea..209da9734ca 100644 --- a/log4j-cassandra/pom.xml +++ b/log4j-cassandra/pom.xml @@ -1,11 +1,11 @@ - + + 4.0.0 + org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT + ${revision} + ../log4j-parent - 4.0.0 log4j-cassandra + Apache Log4j Cassandra - - Cassandra appender for Log4j. - + Cassandra appender for Log4j. + - ${basedir}/.. - Cassandra Documentation - /log4j-cassandra - org.apache.logging.log4j.cassandra + + + + + cassandra.driver.core;substitute="cassandra-driver-core";transitive=false;static=true + + org.apache.logging.log4j.core + + + + 25.1-jre + + 2.2.17 + 1.1.10.7 + + + + + com.github.jnr + jnr-ffi + ${jnr-ffi.version} + + + + org.xerial.snappy + snappy-java + ${snappy.version} + + + + + org.apache.logging.log4j @@ -45,156 +77,60 @@ com.datastax.cassandra cassandra-driver-core - - - junit - junit - - org.mockito - mockito-core + org.apache.logging.log4j + log4j-api-test test + + + com.google.guava + guava + + org.apache.logging.log4j - log4j-api - test-jar + log4j-core-test + test + + + com.google.guava + guava + + org.apache.logging.log4j - log4j-core - test-jar + log4j-slf4j-impl + test org.apache.cassandra cassandra-all - 2.2.8 test - - - ch.qos.logback - logback-classic - - - ch.qos.logback - logback-core - - - 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 - - - - org.apache.felix - maven-bundle-plugin - - - org.apache.logging.log4j.core - * - - - - - - - - - org.apache.maven.plugins - maven-changes-plugin - ${changes.plugin.version} - - - - changes-report - - - - - %URL%/show_bug.cgi?id=%ISSUE% - true - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${checkstyle.plugin.version} - - - ${log4jParentDir}/checkstyle.xml - ${log4jParentDir}/checkstyle-suppressions.xml - false - basedir=${basedir} - licensedir=${log4jParentDir}/checkstyle-header.txt - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc.plugin.version} - - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
- 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.

]]>
- - false - true -
- - - non-aggregate - - javadoc - - - -
- - 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 - - jxr - - - - aggregate - - aggregate - - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${pmd.plugin.version} - - ${maven.compiler.target} - - -
-
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..01b0b1acef9 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.cassandra; @@ -22,6 +22,7 @@ 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.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; @@ -36,12 +37,20 @@ * @see SocketAddress * @see ColumnMapping */ -@Plugin(name = "Cassandra", category = Core.CATEGORY_NAME, elementType = CassandraAppender.ELEMENT_TYPE, printObject = true) +@Plugin( + name = "Cassandra", + category = Core.CATEGORY_NAME, + elementType = CassandraAppender.ELEMENT_TYPE, + printObject = true) public class CassandraAppender extends AbstractDatabaseAppender { - private CassandraAppender(final String name, final Filter filter, final boolean ignoreExceptions, - final CassandraManager manager) { - super(name, filter, ignoreExceptions, manager); + private CassandraAppender( + final String name, + final Filter filter, + final boolean ignoreExceptions, + final Property[] properties, + final CassandraManager manager) { + super(name, filter, null, ignoreExceptions, properties, manager); } @PluginBuilderFactory @@ -50,7 +59,7 @@ public static > B newBuilder() { } public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.core.util.Builder { /** * List of Cassandra node contact points. Addresses without a port (or port set to 0) will use the default @@ -58,7 +67,7 @@ public static class Builder> extends AbstractAppender.Build */ @PluginElement("ContactPoints") @Required(message = "No Cassandra servers provided") - private SocketAddress[] contactPoints = new SocketAddress[]{SocketAddress.getLoopback()}; + private SocketAddress[] contactPoints = new SocketAddress[] {SocketAddress.getLoopback()}; /** * List of column mappings to convert a LogEvent into a database row. @@ -174,12 +183,21 @@ public B setBatchType(final BatchStatement.Type batchType) { @Override 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); + final CassandraManager manager = CassandraManager.getManager( + getName(), + contactPoints, + columns, + useTls, + clusterName, + keyspace, + table, + username, + password, + useClockForTimestampGenerator, + bufferSize, + batched, + batchType); + return new CassandraAppender(getName(), getFilter(), isIgnoreExceptions(), null, 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..64c5221044e 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 @@ -1,32 +1,31 @@ /* * 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.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; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Session; +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; @@ -57,9 +56,14 @@ public class CassandraManager extends AbstractDatabaseManager { private Session session; private PreparedStatement preparedStatement; - private CassandraManager(final String name, final int bufferSize, final Cluster cluster, - final String keyspace, final String insertQueryTemplate, - final List columnMappings, final BatchStatement batchStatement) { + private CassandraManager( + final String name, + final int bufferSize, + final Cluster cluster, + final String keyspace, + final String insertQueryTemplate, + final List columnMappings, + final BatchStatement batchStatement) { super(name, bufferSize); this.cluster = cluster; this.keyspace = keyspace; @@ -87,26 +91,21 @@ 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++) { final ColumnMapping columnMapping = columnMappings.get(i); if (ThreadContextMap.class.isAssignableFrom(columnMapping.getType()) - || ReadOnlyStringMap.class.isAssignableFrom(columnMapping.getType())) { + || ReadOnlyStringMap.class.isAssignableFrom(columnMapping.getType())) { values[i] = event.getContextData().toMap(); } else if (ThreadContextStack.class.isAssignableFrom(columnMapping.getType())) { values[i] = event.getContextStack().asList(); } else if (Date.class.isAssignableFrom(columnMapping.getType())) { - values[i] = DateTypeConverter.fromMillis(event.getTimeMillis(), columnMapping.getType().asSubclass(Date.class)); + 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] = TypeConverters.convert( + columnMapping.getLayout().toSerializable(event), columnMapping.getType(), null); } } final BoundStatement boundStatement = preparedStatement.bind(values); @@ -125,15 +124,36 @@ protected boolean commitAndClose() { return true; } - public static CassandraManager getManager(final String name, final SocketAddress[] contactPoints, - final ColumnMapping[] columns, final boolean useTls, - final String clusterName, final String keyspace, final String table, - final String username, final String password, - final boolean useClockForTimestampGenerator, final int bufferSize, - final boolean batched, final BatchStatement.Type batchType) { - return getManager(name, - new FactoryData(contactPoints, columns, useTls, clusterName, keyspace, table, username, password, - useClockForTimestampGenerator, bufferSize, batched, batchType), CassandraManagerFactory.INSTANCE); + public static CassandraManager getManager( + final String name, + final SocketAddress[] contactPoints, + final ColumnMapping[] columns, + final boolean useTls, + final String clusterName, + final String keyspace, + final String table, + final String username, + final String password, + final boolean useClockForTimestampGenerator, + final int bufferSize, + final boolean batched, + final BatchStatement.Type batchType) { + return getManager( + name, + new FactoryData( + contactPoints, + columns, + useTls, + clusterName, + keyspace, + table, + username, + password, + useClockForTimestampGenerator, + bufferSize, + batched, + batchType), + CassandraManagerFactory.INSTANCE); } private static class CassandraManagerFactory implements ManagerFactory { @@ -143,8 +163,8 @@ private static class CassandraManagerFactory implements ManagerFactoryCassandra Appender manual * @since 2.8 */ -package org.apache.logging.log4j.cassandra; \ No newline at end of file +@Export +@Open("org.apache.logging.log4j.core") +@Version("2.20.1") +package org.apache.logging.log4j.cassandra; + +import aQute.bnd.annotation.jpms.Open; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-cassandra/src/site/markdown/index.md.vm b/log4j-cassandra/src/site/markdown/index.md.vm deleted file mode 100644 index 8a9c70bf279..00000000000 --- a/log4j-cassandra/src/site/markdown/index.md.vm +++ /dev/null @@ -1,24 +0,0 @@ - - -#set($h1='#') -#set($h2='##') -## TODO: use properties for dynamic dependency versions - -$h1 Cassandra Appender - -The Cassandra Appender allow applications to send events to Apache Cassandra repositories. diff --git a/log4j-cassandra/src/site/site.xml b/log4j-cassandra/src/site/site.xml deleted file mode 100644 index 7c51066b39a..00000000000 --- a/log4j-cassandra/src/site/site.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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..6e1a94975e5 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 @@ -1,58 +1,65 @@ /* * 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.logging.log4j.cassandra; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; 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.Row; -import com.datastax.driver.core.Session; +import org.apache.commons.lang3.SystemUtils; 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.apache.logging.log4j.core.test.categories.Appenders; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Assume; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; -import static org.junit.Assert.*; - /** * Integration test for CassandraAppender. */ @Category(Appenders.Cassandra.class) public class CassandraAppenderIT { - private static final String DDL = "CREATE TABLE logs (" + - "id timeuuid PRIMARY KEY," + - "timeid timeuuid," + - "message text," + - "level text," + - "marker text," + - "logger text," + - "timestamp timestamp," + - "mdc map," + - "ndc list" + - ")"; + @BeforeClass + public static void disbaleOnAarch64() { + Assume.assumeFalse(SystemUtils.OS_ARCH.equalsIgnoreCase("aarch64")); + } + + private static final String DDL = "CREATE TABLE logs (" + "id timeuuid PRIMARY KEY," + + "timeid timeuuid," + + "message text," + + "level text," + + "marker text," + + "logger text," + + "timestamp timestamp," + + "mdc map," + + "ndc list" + + ")"; private static final LoggerContextRule CTX = new LoggerContextRule("CassandraAppenderTest.xml"); private static final CassandraRule CASSANDRA = new CassandraRule("test", DDL); 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 index 2939d07f935..8ae051b2a5b 100644 --- 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 @@ -1,32 +1,41 @@ /* * 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.logging.log4j.cassandra; +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SocketOptions; +import io.netty.channel.socket.ServerSocketChannel; import java.io.IOException; -import java.net.InetAddress; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; import java.nio.file.Files; import java.nio.file.Path; import java.security.Permission; +import java.util.Collection; 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.cassandra.service.NativeTransportService; +import org.apache.cassandra.transport.Server; +import org.apache.cassandra.transport.Server.ConnectionTracker; import org.apache.logging.log4j.LoggingException; import org.apache.logging.log4j.core.util.Cancellable; import org.apache.logging.log4j.core.util.Closer; @@ -42,7 +51,7 @@ 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 EmbeddedCassandra embeddedCassandra = new EmbeddedCassandra(latch); private final String keyspace; private final String tableDdl; private Cluster cluster; @@ -64,17 +73,26 @@ public Session connect() { protected void before() throws Throwable { final Path root = Files.createTempDirectory("cassandra"); Files.createDirectories(root.resolve("data")); - final Path config = root.resolve("cassandra.yml"); + final Path config = root.resolve("cassandra.yaml"); Files.copy(getClass().getResourceAsStream("/cassandra.yaml"), config); - System.setProperty("cassandra.config", "file:" + config.toString()); + System.setProperty("cassandra.native_transport_port", "0"); + System.setProperty("cassandra.storage_port", "0"); + System.setProperty("cassandra.config", "file:" + config); 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(); + final InetSocketAddress nativeSocket = embeddedCassandra.getNativeSocket(); + assertNotNull(nativeSocket); + System.setProperty("cassandra.native_transport_port", Integer.toString(nativeSocket.getPort())); + cluster = Cluster.builder() + .addContactPointsWithPorts(nativeSocket) + .withSocketOptions(new SocketOptions().setConnectTimeoutMillis(60000)) + .build(); + try (final Session session = cluster.connect()) { - session.execute("CREATE KEYSPACE " + keyspace + " WITH REPLICATION = " + - "{ 'class': 'SimpleStrategy', 'replication_factor': 2 };"); + session.execute("CREATE KEYSPACE " + keyspace + " WITH REPLICATION = " + + "{ 'class': 'SimpleStrategy', 'replication_factor': 2 };"); } try (final Session session = connect()) { session.execute(tableDdl); @@ -87,9 +105,9 @@ protected void after() { embeddedCassandra.cancel(); } - private static class EmbeddedCassandra implements Cancellable { + private static final class EmbeddedCassandra implements Cancellable { - private final CassandraDaemon daemon = new CassandraDaemon(); + private final CassandraDaemon daemon = CassandraDaemon.getInstanceForTesting(); private final CountDownLatch latch; private EmbeddedCassandra(final CountDownLatch latch) { @@ -129,6 +147,7 @@ public void checkPermission(final Permission permission) { @Override public void run() { + daemon.applyConfig(); try { daemon.init(null); } catch (final IOException e) { @@ -137,5 +156,32 @@ public void run() { daemon.start(); latch.countDown(); } + + public InetSocketAddress getNativeSocket() { + try { + final Field nativeServiceField = CassandraDaemon.class.getDeclaredField("nativeTransportService"); + nativeServiceField.setAccessible(true); + final NativeTransportService nativeService = (NativeTransportService) nativeServiceField.get(daemon); + final Field serversField = NativeTransportService.class.getDeclaredField("servers"); + serversField.setAccessible(true); + @SuppressWarnings("unchecked") + final Collection servers = (Collection) serversField.get(nativeService); + if (!servers.isEmpty()) { + final Server server = servers.iterator().next(); + final Field trackerField = Server.class.getDeclaredField("connectionTracker"); + trackerField.setAccessible(true); + final ConnectionTracker connectionTracker = (ConnectionTracker) trackerField.get(server); + final ServerSocketChannel serverChannel = connectionTracker.allChannels.stream() + .filter(ServerSocketChannel.class::isInstance) + .map(ServerSocketChannel.class::cast) + .findFirst() + .orElse(null); + return serverChannel.localAddress(); + } + } catch (ReflectiveOperationException | ClassCastException e) { + fail(e); + } + return null; + } } } diff --git a/log4j-cassandra/src/test/resources/CassandraAppenderTest.xml b/log4j-cassandra/src/test/resources/CassandraAppenderTest.xml index e1f37e93c7d..8ca15d12311 100644 --- a/log4j-cassandra/src/test/resources/CassandraAppenderTest.xml +++ b/log4j-cassandra/src/test/resources/CassandraAppenderTest.xml @@ -1,25 +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. + --> - + @@ -37,4 +36,4 @@ - \ No newline at end of file + diff --git a/log4j-cassandra/src/test/resources/cassandra.yaml b/log4j-cassandra/src/test/resources/cassandra.yaml index 356e43dc2db..829dd027867 100644 --- a/log4j-cassandra/src/test/resources/cassandra.yaml +++ b/log4j-cassandra/src/test/resources/cassandra.yaml @@ -1,17 +1,19 @@ +# # 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. +# # Cassandra storage config YAML @@ -35,13 +37,13 @@ cluster_name: 'Test Cluster' # Specifying initial_token will override this setting on the node's initial start, # on subsequent starts, this setting will apply even if initial token is set. # -# If you already have a cluster with 1 token per node, and wish to migrate to +# If you already have a cluster with 1 token per node, and wish to migrate to # multiple tokens per node, see http://wiki.apache.org/cassandra/Operations num_tokens: 256 # initial_token allows you to specify tokens manually. While you can use # it with -# vnodes (num_tokens > 1, above) -- in which case you should provide a -# comma-separated list -- it's primarily used when adding nodes # to legacy clusters +# vnodes (num_tokens > 1, above) -- in which case you should provide a +# comma-separated list -- it's primarily used when adding nodes # to legacy clusters # that do not have vnodes enabled. # initial_token: @@ -266,8 +268,8 @@ counter_cache_save_period: 7200 # If not set, the default directory is $CASSANDRA_HOME/data/saved_caches. # saved_caches_directory: /var/lib/cassandra/saved_caches -# commitlog_sync may be either "periodic" or "batch." -# +# commitlog_sync may be either "periodic" or "batch." +# # When in batch mode, Cassandra won't ack writes until the commit log # has been fsynced to disk. It will wait # commitlog_sync_batch_window_in_ms milliseconds between fsyncs. @@ -280,14 +282,14 @@ counter_cache_save_period: 7200 # # the other option is "periodic" where writes may be acked immediately # and the CommitLog is simply synced every commitlog_sync_period_in_ms -# milliseconds. +# milliseconds. commitlog_sync: periodic commitlog_sync_period_in_ms: 10000 # The size of the individual commitlog file segments. A commitlog # segment may be archived, deleted, or recycled once all the data # in it (potentially from each columnfamily in the system) has been -# flushed to sstables. +# flushed to sstables. # # The default size is 32, which is almost always fine, but if you are # archiving commitlog segments (see commitlog_archiving.properties), @@ -306,7 +308,7 @@ commitlog_segment_size_in_mb: 32 # any class that implements the SeedProvider interface and has a # constructor that takes a Map of parameters will do. seed_provider: - # Addresses of hosts that are deemed contact points. + # Addresses of hosts that are deemed contact points. # Cassandra nodes use this list of hosts to find each other and learn # the topology of the ring. You must change this if you are running # multiple nodes! @@ -335,7 +337,7 @@ concurrent_counter_writes: 32 # the smaller of 1/4 of heap or 512MB. # file_cache_size_in_mb: 512 -# Total permitted memory to use for memtables. Cassandra will stop +# Total permitted memory to use for memtables. Cassandra will stop # accepting writes when the limit is exceeded until a flush completes, # and will trigger a flush based on memtable_cleanup_threshold # If omitted, Cassandra will set both to 1/4 the size of the heap. @@ -371,16 +373,16 @@ memtable_allocation_type: heap_buffers # This sets the amount of memtable flush writer threads. These will # be blocked by disk io, and each one will hold a memtable in memory -# while blocked. +# while blocked. # # memtable_flush_writers defaults to the smaller of (number of disks, # number of cores), with a minimum of 2 and a maximum of 8. -# +# # If your data directories are backed by SSD, you should increase this # 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 +551,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 @@ -575,7 +577,7 @@ incremental_backups: false snapshot_before_compaction: false # Whether or not a snapshot is taken of the data before keyspace truncation -# or dropping of column families. The STRONGLY advised default of true +# or dropping of column families. The STRONGLY advised default of true # should be used to provide data safety. If you set this flag to false, you will # lose data on truncation or drop. auto_snapshot: true @@ -625,7 +627,7 @@ unlogged_batch_across_partitions_warn_threshold: 10 # # concurrent_compactors defaults to the smaller of (number of disks, # number of cores), with a minimum of 2 and a maximum of 8. -# +# # If your data directories are backed by SSD, you should increase this # to the number of cores. #concurrent_compactors: 1 @@ -643,7 +645,7 @@ compaction_large_partition_warning_threshold_mb: 100 # When compacting, the replacement sstable(s) can be opened before they # are completely written, and used in place of the prior sstables for -# any range that has been written. This helps to smoothly transfer reads +# any range that has been written. This helps to smoothly transfer reads # between the sstables, reducing page cache churn and keeping hot rows hot sstable_preemptive_open_interval_in_mb: 50 @@ -682,7 +684,7 @@ request_timeout_in_ms: 10000 # Enable operation timeout information exchange between nodes to accurately # measure request timeouts. If disabled, replicas will assume that requests # were forwarded to them instantly by the coordinator, which means that -# under overload conditions we will waste that much extra time processing +# under overload conditions we will waste that much extra time processing # already-timed-out requests. # # Warning: before enabling this property make sure to ntp is installed @@ -761,7 +763,7 @@ endpoint_snitch: SimpleSnitch # controls how often to perform the more expensive part of host score # calculation -dynamic_snitch_update_interval_in_ms: 100 +dynamic_snitch_update_interval_in_ms: 100 # controls how often to reset all host scores, allowing a bad host to # possibly recover dynamic_snitch_reset_interval_in_ms: 600000 @@ -791,7 +793,7 @@ request_scheduler: org.apache.cassandra.scheduler.NoScheduler # NoScheduler - Has no options # RoundRobin # - throttle_limit -- The throttle_limit is the number of in-flight -# requests per client. Requests beyond +# requests per client. Requests beyond # that limit are queued up until # running requests can complete. # The value of 80 here is twice the number of diff --git a/log4j-core-fuzz-test/pom.xml b/log4j-core-fuzz-test/pom.xml new file mode 100644 index 00000000000..85bb659a782 --- /dev/null +++ b/log4j-core-fuzz-test/pom.xml @@ -0,0 +1,51 @@ + + + + + 4.0.0 + + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + + log4j-core-fuzz-test + + Apache Log4j Core fuzz tests + + + true + true + true + true + true + true + + + + + + org.apache.logging.log4j + log4j-fuzz-test + + + + + diff --git a/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.java b/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.java new file mode 100644 index 00000000000..a2419911260 --- /dev/null +++ b/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.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.fuzz; + +import static org.apache.logging.log4j.fuzz.FuzzingUtil.createLoggerContext; +import static org.apache.logging.log4j.fuzz.FuzzingUtil.fuzzLogger; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.fuzz.EncodingAppender; +import org.apache.logging.log4j.fuzz.FuzzingUtil.Log4jLoggerFacade; +import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade; +import org.apache.logging.log4j.spi.ExtendedLogger; + +public final class PatternLayoutFuzzer { + + public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) { + final String loggerContextName = PatternLayoutFuzzer.class.getSimpleName() + "LoggerContext"; + try (final LoggerContext loggerContext = + createLoggerContext(loggerContextName, EncodingAppender.PLUGIN_NAME, configBuilder -> configBuilder + .newLayout("PatternLayout") + // Enforce using a single message-based converter, i.e., `MessagePatternConverter` + .addAttribute("pattern", "%m"))) { + final ExtendedLogger logger = loggerContext.getLogger(PatternLayoutFuzzer.class); + final LoggerFacade loggerFacade = new Log4jLoggerFacade(logger); + fuzzLogger(loggerFacade, dataProvider); + } + } +} diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml index 9df2f43fca6..e657b86c210 100644 --- a/log4j-core-its/pom.xml +++ b/log4j-core-its/pom.xml @@ -1,75 +1,78 @@ + 4.0.0 + org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent + log4j-core-its - jar + Apache Log4j Core Integration Tests - Integration Tests for the Apache Log4j Implementation + - ${basedir}/.. - Core Documentation - /core + + true + true + true + true + true + true + + + 2.0.17 + + + + + + org.slf4j + slf4j-api + ${slf4j2.version} + + + + 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 - true com.conversantmedia disruptor - jdk7 true - + - org.jctools - jctools-core + com.lmax + disruptor true @@ -84,116 +87,136 @@ 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.jms + javax.jms-api true - + - com.fasterxml.woodstox - woodstox-core - 5.0.3 + org.jctools + jctools-core true - - - - log4j - log4j - 1.2.17 + org.apache.logging.log4j + log4j-api-test test - - org.slf4j - slf4j-api + org.apache.logging.log4j + log4j-core-test test + - org.slf4j - slf4j-ext + org.apache.activemq + activemq-broker test + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + + - - junit - junit + commons-logging + commons-logging test org.hamcrest - hamcrest-all + hamcrest test - + - org.springframework - spring-test + org.hdrhistogram + HdrHistogram test - commons-logging - commons-logging + org.junit.jupiter + junit-jupiter-engine test - + - ch.qos.logback - logback-core + org.junit.vintage + junit-vintage-engine test + + - ch.qos.logback - logback-classic + log4j + log4j test - - - org.jboss.spec.javax.jms - jboss-jms-api_1.1_spec - provided - true - - + - org.apache.activemq - activemq-broker + ch.qos.logback + logback-classic test - - - org.apache.geronimo.specs - geronimo-jms_1.1_spec - - + + - maven-compiler-plugin + org.apache.maven.plugins + maven-enforcer-plugin - test-compile - - testCompile - - test-compile + ban-logging-dependencies + + + + + ch.qos.logback:*:*:*:test + + + + + - org.apache.felix - maven-bundle-plugin + 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-jar-plugin @@ -212,6 +235,7 @@ + org.apache.maven.plugins maven-source-plugin @@ -233,40 +257,14 @@ + 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..761121341a4 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 @@ -1,44 +1,41 @@ /* * 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.logging.log4j; import java.util.Collections; import java.util.HashMap; import java.util.Map; - -import org.apache.logging.log4j.categories.PerformanceTests; import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.slf4j.MDC; /** * */ -@Category(PerformanceTests.class) -public class FilterPerformanceComparison { +@Tag("PerformanceTests") +class FilterPerformanceComparison { private final Logger logger = LogManager.getLogger(FilterPerformanceComparison.class.getName()); private final org.slf4j.Logger logbacklogger = org.slf4j.LoggerFactory.getLogger(FilterPerformanceComparison.class); - // How many times should we try to log: private static final int COUNT = 10000000; private static final int THREADED_COUNT = 100000; @@ -49,30 +46,30 @@ public class FilterPerformanceComparison { private static final String LOGBACK_CONF = "logback.configurationFile"; - @BeforeClass - public static void setupClass() { + @BeforeAll + static void setupClass() { System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG); System.setProperty(LOGBACK_CONF, LOGBACK_CONFIG); } - @AfterClass - public static void cleanupClass() { + @AfterAll + static void cleanupClass() { System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); System.clearProperty(LOGBACK_CONF); } - @After - public void after() { + @AfterEach + void after() { ThreadContext.clearAll(); } @Test - public void testPerformanceEmptyContext() throws Exception { - testPerformance(Collections.emptyMap()); + void testPerformanceEmptyContext() throws Exception { + testPerformance(Collections.emptyMap()); } @Test - public void testPerformanceNonEmptyContext() throws Exception { + void testPerformanceNonEmptyContext() throws Exception { testPerformance(createNonEmptyContextData()); } @@ -90,17 +87,17 @@ private static void putContextData(final Map contextData) { } } - private void testPerformance(final Map contextData) throws Exception { + private void testPerformance(final Map contextData) { putContextData(contextData); Target.LOGBACK.timedLoop(logger, logbacklogger, WARMUP); Target.LOG4J2.timedLoop(logger, logbacklogger, WARMUP); - System.out.println("Single-threaded Log4j 2.0, " - + (contextData.isEmpty() ? "EMPTY context" : "NON-EMPTY context")); + System.out.println( + "Single-threaded Log4j 2.0, " + (contextData.isEmpty() ? "EMPTY context" : "NON-EMPTY context")); final long result3 = Target.LOG4J2.timedLoop(logger, logbacklogger, COUNT); - System.out.println("Single-threaded Logback, " - + (contextData.isEmpty() ? "EMPTY context" : "NON-EMPTY context")); + System.out.println( + "Single-threaded Logback, " + (contextData.isEmpty() ? "EMPTY context" : "NON-EMPTY context")); final long result2 = Target.LOGBACK.timedLoop(logger, logbacklogger, COUNT); @@ -111,12 +108,12 @@ private void testPerformance(final Map contextData) throws Excep } @Test - public void testThreadsEmptyContext() throws Exception { - testThreads(Collections.emptyMap()); + void testThreadsEmptyContext() throws Exception { + testThreads(Collections.emptyMap()); } @Test - public void testThreadsNonEmptyContext() throws Exception { + void testThreadsNonEmptyContext() throws Exception { testThreads(createNonEmptyContextData()); } @@ -129,33 +126,33 @@ private void testThreads(final Map contextData) throws Exception + (contextData.isEmpty() ? "EMPTY context" : "NON-EMPTY context")); final Worker[] workers = new Worker[threadCount]; final long[] results = new long[threadCount]; - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i] = new Worker(Target.LOG4J2, threadedCount, results, i, contextData); } - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i].start(); } long total = 0; - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i].join(); total += results[i]; } final long result3 = total / threadCount; total = 0; - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i] = new Worker(Target.LOGBACK, threadedCount, results, i, contextData); } - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i].start(); } - for (int i=0; i < threadCount; ++i) { + for (int i = 0; i < threadCount; ++i) { workers[i].join(); total += results[i]; } final long result2 = total / threadCount; System.out.println("###############################################"); System.out.println("Logback: " + result2); - System.out.println("Log4j 2.0: " + result3 ); + System.out.println("Log4j 2.0: " + result3); System.out.println("###############################################"); } } @@ -184,6 +181,7 @@ long timedLoop(final Logger logger, final org.slf4j.Logger logbacklogger, final return (System.nanoTime() - start) / loop; } }; + abstract long timedLoop(final Logger logger, final org.slf4j.Logger logbacklogger, final int loop); } @@ -195,7 +193,11 @@ private class Worker extends Thread { private final int index; private final Map contextData; - public Worker(final Target target, final int count, final long[] results, final int index, + public Worker( + final Target target, + final int count, + final long[] results, + final int index, final Map contextData) { this.target = target; this.count = count; @@ -210,5 +212,4 @@ public void run() { results[index] = target.timedLoop(logger, logbacklogger, count); } } - -} \ 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..93ce3350f57 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j; @@ -24,26 +24,23 @@ import java.io.Writer; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; - -import org.apache.logging.log4j.categories.PerformanceTests; import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.util.Profiler; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.apache.logging.log4j.core.test.util.Profiler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; /** * Use this class to analyze performance between Log4j and other logging frameworks. */ -@Category(PerformanceTests.class) -public class PerformanceComparison { +@Tag("PerformanceTests") +class PerformanceComparison { private final Logger logger = LogManager.getLogger(PerformanceComparison.class.getName()); private final org.slf4j.Logger logbacklogger = org.slf4j.LoggerFactory.getLogger(PerformanceComparison.class); private final org.apache.log4j.Logger log4jlogger = org.apache.log4j.Logger.getLogger(PerformanceComparison.class); - // How many times should we try to log: private static final int COUNT = 500000; private static final int PROFILE_COUNT = 500000; @@ -56,15 +53,15 @@ public class PerformanceComparison { private static final String LOGBACK_CONF = "logback.configurationFile"; private static final String LOG4J_CONF = "log4j.configuration"; - @BeforeClass - public static void setupClass() { + @BeforeAll + static void setupClass() { System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG); System.setProperty(LOGBACK_CONF, LOGBACK_CONFIG); System.setProperty(LOG4J_CONF, LOG4J_CONFIG); } - @AfterClass - public static void cleanupClass() { + @AfterAll + static void cleanupClass() { System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); System.clearProperty(LOGBACK_CONF); System.clearProperty(LOG4J_CONF); @@ -74,7 +71,7 @@ public static void cleanupClass() { } @Test - public void testPerformance() throws Exception { + void testPerformance() { log4j(WARMUP); logback(WARMUP); @@ -109,8 +106,8 @@ private void doRun() { System.out.println("###############################################"); } - //@Test - public void testRawPerformance() throws Exception { + // @Test + private void testRawPerformance() throws Exception { final OutputStream os = new FileOutputStream("target/testos.log", true); final long result1 = writeToStream(COUNT, os); os.close(); @@ -150,7 +147,6 @@ private long logback(final int loop) { return (System.nanoTime() - start) / loop; } - private long log4j2(final int loop) { final Integer j = Integer.valueOf(2); final long start = System.nanoTime(); @@ -160,7 +156,6 @@ private long log4j2(final int loop) { return (System.nanoTime() - start) / loop; } - private long writeToWriter(final int loop, final Writer w) throws Exception { final Integer j = Integer.valueOf(2); final long start = System.nanoTime(); @@ -181,7 +176,7 @@ private long writeToStream(final int loop, final OutputStream os) throws Excepti private long writeToChannel(final int loop, final FileChannel channel) throws Exception { final Integer j = Integer.valueOf(2); - final ByteBuffer buf = ByteBuffer.allocateDirect(8*1024); + final ByteBuffer buf = ByteBuffer.allocateDirect(8 * 1024); final long start = System.nanoTime(); for (int i = 0; i < loop; i++) { channel.write(getByteBuffer(buf, "SEE IF THIS IS LOGGED " + j + '.')); @@ -199,5 +194,4 @@ private ByteBuffer getByteBuffer(final ByteBuffer buf, final String s) { 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..253b8f70cba 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j; @@ -23,9 +23,8 @@ import java.io.Writer; 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; @@ -51,7 +50,7 @@ public class PerformanceRun { private static final int COUNT = 1000000; @Test - public void testPerformance() throws Exception { + public void testPerformance() { System.out.println("Starting Log4j 2.0"); final long result3 = log4j2(COUNT); @@ -132,5 +131,4 @@ private ByteBuffer getByteBuffer(final ByteBuffer buf, final String s) { private byte[] getBytes(final String s) { return s.getBytes(); } - } 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..e3c1c7beb03 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 @@ -1,40 +1,38 @@ /* * 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.logging.log4j.core; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.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.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; /** * */ -@Category(PerformanceTests.class) -public class SimplePerfTest { +@Tag("PerformanceTests") +class SimplePerfTest { private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(SimplePerfTest.class.getName()); private volatile Level lvl = Level.DEBUG; @@ -45,69 +43,72 @@ public class SimplePerfTest { private static int RAND_SIZE = 250; private static int[] values = new int[RAND_SIZE]; - @BeforeClass - public static void setupClass() { + @BeforeAll + static void setupClass() { + + final Configuration config = LoggerContext.getContext().getConfiguration(); - 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()); - } + if (!DefaultConfiguration.DEFAULT_NAME.equals(config.getName())) { + System.out.println("Configuration was " + config.getName()); + LoggerContext.getContext().start(new DefaultConfiguration()); + } - for (int i=0; i < WARMUP; ++i) { + for (int i = 0; i < WARMUP; ++i) { overhead(); } System.gc(); final Timer timer = new Timer("Setup", LOOP_CNT); timer.start(); - for (int i=0; i < (LOOP_CNT / 150); ++i) { + for (int i = 0; i < (LOOP_CNT / 150); ++i) { overhead(); } timer.stop(); maxTime = timer.getElapsedNanoTime(); System.gc(); - System.out.println(timer.toString()); + System.out.println(timer); } @Test - public void debugDisabled() { + void debugDisabled() throws Exception { System.gc(); + Thread.sleep(100); final Timer timer = new Timer("DebugDisabled", LOOP_CNT); timer.start(); - for (int i=0; i < LOOP_CNT; ++i) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.isDebugEnabled(); } timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + System.out.println(timer); + assertTrue(maxTime > timer.getElapsedNanoTime(), "Timer exceeded max time of " + maxTime); } @Test - public void debugDisabledByLevel() { + void debugDisabledByLevel() throws Exception { System.gc(); - final Timer timer = new Timer("DebugDisabled", LOOP_CNT); + Thread.sleep(100); + final Timer timer = new Timer("IsEnabled", LOOP_CNT); timer.start(); - for (int i=0; i < LOOP_CNT; ++i) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.isEnabled(Level.DEBUG); } timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + System.out.println(timer); + assertTrue(maxTime > timer.getElapsedNanoTime(), "Timer exceeded max time of " + maxTime); } @Test - public void debugLogger() { + void debugLogger() throws Exception { 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) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.debug(msg); } timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + System.out.println(timer); + assertTrue(maxTime > timer.getElapsedNanoTime(), "Timer exceeded max time of " + maxTime); } /* @Test @@ -140,6 +141,7 @@ private static class SimpleRandom extends Random { * Generated serial version ID. */ private static final long serialVersionUID = 3517002855516031846L; + private int low = 5; private int high = 55; @@ -158,10 +160,10 @@ 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++) { - if (array[j-1] > array[j]) { - final int temp = array[j-1]; - array[j-1] = array[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]; array[j] = temp; } } 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..c3590118521 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 @@ -1,83 +1,82 @@ /* * 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.logging.log4j.core; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - 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.junit.Test; -import org.junit.experimental.categories.Category; +import org.apache.logging.log4j.util.Timer; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; /** * */ -@Category(PerformanceTests.class) -public class ThreadedPerfTest { +@Tag("PerformanceTests") +class ThreadedPerfTest { - private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(ThreadedPerfTest.class.getName()); + private static final org.apache.logging.log4j.Logger logger = + LogManager.getLogger(ThreadedPerfTest.class.getName()); private volatile Level lvl = Level.DEBUG; private static final int LOOP_CNT = 10000000; private static final int THREADS = 10; @Test - public void debugDisabled() { + void debugDisabled() { final Timer timer = new Timer("DebugDisabled", LOOP_CNT * THREADS); final Runnable runnable = new DebugDisabledRunnable(); final ExecutorService pool = Executors.newFixedThreadPool(THREADS); timer.start(); - for (int i=0; i < THREADS; ++i) { + for (int i = 0; i < THREADS; ++i) { pool.execute(runnable); } pool.shutdown(); timer.stop(); - System.out.println(timer.toString()); + System.out.println(timer); } @Test - public void debugLogger() { + void debugLogger() { final Timer timer = new Timer("DebugLogger", LOOP_CNT * THREADS); final Runnable runnable = new DebugLoggerRunnable(); final ExecutorService pool = Executors.newFixedThreadPool(THREADS); timer.start(); - for (int i=0; i < THREADS; ++i) { + for (int i = 0; i < THREADS; ++i) { pool.execute(runnable); } pool.shutdown(); timer.stop(); - System.out.println(timer.toString()); + System.out.println(timer); } public static class DebugDisabledRunnable implements Runnable { @Override public void run() { - for (int i=0; i < LOOP_CNT; ++i) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.isDebugEnabled(); } } } - public static class DebugLoggerRunnable implements Runnable { + public static class DebugLoggerRunnable implements Runnable { @Override public void run() { - for (int i=0; i < LOOP_CNT; ++i) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.debug("This is a test"); } } 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..ce8a7d83543 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; @@ -20,23 +20,19 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; - 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.junit.LoggerContextRule; import org.junit.ClassRule; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.junit.rules.RuleChain; -import org.junit.rules.TestRule; -import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * */ -@Category(PerformanceTests.class) +@Tag("PerformanceTests") public class ThreadedTest { private static final String DIR = "target/threaded"; private static final String CONFIG = "log4j-threaded.xml"; @@ -50,10 +46,7 @@ public class ThreadedTest { // this would look pretty sweet with lambdas @ClassRule - public static RuleChain chain = RuleChain.outerRule(new TestRule() { - @Override - public Statement apply(final Statement base, final Description description) { - return new Statement() { + public static RuleChain chain = RuleChain.outerRule((base, description) -> new Statement() { @Override public void evaluate() throws Throwable { deleteDir(); @@ -63,15 +56,14 @@ public void evaluate() throws Throwable { deleteDir(); } } - }; - } - }).around(context); + }) + .around(context); @Test - public void testDeadlock() throws Exception { + void testDeadlock() throws Exception { final ExecutorService pool = Executors.newFixedThreadPool(THREADS * 2); final State state = new State(); - for (int count=0; count < THREADS; ++count) { + for (int count = 0; count < THREADS; ++count) { pool.execute(new LoggingRunnable(state)); pool.execute(new StateSettingRunnable(state)); } @@ -86,22 +78,25 @@ public class LoggingRunnable implements Runnable { public LoggingRunnable(final State state) { this.state = state; } + @Override public void run() { - for (int i=0; i < LOOP_CNT; ++i) { + for (int i = 0; i < LOOP_CNT; ++i) { logger.debug(state); } } } + public class StateSettingRunnable implements Runnable { private final State state; public StateSettingRunnable(final State state) { this.state = state; } + @Override public void run() { - for (int i=0; i < LOOP_CNT*4; ++i) { + for (int i = 0; i < LOOP_CNT * 4; ++i) { Thread.yield(); state.setState(); } diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java new file mode 100644 index 00000000000..fbf567bb4d7 --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.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.perftest; + +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import org.apache.logging.log4j.core.async.perftest.ResponseTimeTest.PrintingAsyncQueueFullPolicy; + +public abstract class AbstractRunQueue implements IPerfTestRunner { + + abstract BlockingQueue createQueue(int capacity); + + private static final String STOP = "STOP_TEST"; + private volatile boolean stopped = false; + private final BlockingQueue queue = createQueue(256 * 1024); + private final Thread backGroundThread; + + AbstractRunQueue() { + backGroundThread = new Thread(() -> { + for (; ; ) { + try { + if (Objects.equals(queue.take(), STOP)) { + break; + } + } catch (final InterruptedException e) { + e.printStackTrace(); + break; + } + } + }); + backGroundThread.start(); + } + + @Override + public void runThroughputTest(final int lines, final Histogram histogram) {} + + @Override + public void runLatencyTest( + final int samples, final Histogram histogram, final long nanoTimeCost, final int threadCount) {} + + @Override + public final void shutdown() { + stopped = true; + try { + queue.put(STOP); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public final void log(final String finalMessage) { + if (stopped) { + return; + } + if (!queue.offer(finalMessage)) { + PrintingAsyncQueueFullPolicy.ringbufferFull.incrementAndGet(); + try { + queue.put(finalMessage); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java new file mode 100644 index 00000000000..5bb4763fa55 --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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) { + final 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; + + Arrays.fill(counts, 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]) { + final long upperBound = Math.min(upperBounds[i], maxValue); + final long midPoint = lowerBound + ((upperBound - lowerBound) / 2L); + + final 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() { + final 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-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java new file mode 100644 index 00000000000..977b191b95f --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.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.async.perftest; + +public interface IPerfTestRunner { + String LINE100 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456"; + String THROUGHPUT_MSG = LINE100 + LINE100 + LINE100 + LINE100 + LINE100; + String LATENCY_MSG = "Short msg"; + + void runThroughputTest(int lines, Histogram histogram); + + void runLatencyTest(int samples, Histogram histogram, long nanoTimeCost, int threadCount); + + void shutdown(); + + void log(String finalMessage); +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java index 65b97df77d6..16ad04c9a9e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -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-its/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java new file mode 100644 index 00000000000..38ee47d0fdc --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.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.async.perftest; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +public class MultiThreadPerfTest extends PerfTest { + + public static void main(final String[] args) throws Exception { + new MultiThreadPerfTest().doMain(args); + } + + @Override + public void runTestAndPrintResult( + final IPerfTestRunner runner, final String name, final int threadCount, final String resultFile) + throws Exception { + + // ThreadContext.put("aKey", "mdcVal"); + PerfTest.println("Warming up the JVM..."); + final long t1 = System.nanoTime(); + + // warmup at least 2 rounds and at most 1 minute + final Histogram warmupHist = PerfTest.createHistogram(); + final long stop = System.nanoTime() + TimeUnit.MINUTES.toNanos(1); + final Runnable run1 = () -> { + for (int i = 0; i < 10; i++) { + final int LINES = PerfTest.throughput ? 50000 : 200000; + runTest(runner, LINES, null, warmupHist, 2); + if (i > 0 && System.nanoTime() - stop >= 0) { + return; + } + } + }; + final Thread thread1 = new Thread(run1); + final Thread thread2 = new Thread(run1); + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + PerfTest.printf("Warmup complete in %.1f seconds%n", (System.nanoTime() - t1) / (1000.0 * 1000.0 * 1000.0)); + PerfTest.println("Waiting 10 seconds for buffers to drain warmup data..."); + Thread.sleep(10000); + new File("perftest.log").delete(); + new File("perftest.log").createNewFile(); + + PerfTest.println("Starting the main test..."); + PerfTest.throughput = false; + multiThreadedTestRun(runner, name, threadCount, resultFile); + + Thread.sleep(1000); + PerfTest.throughput = true; + multiThreadedTestRun(runner, name, threadCount, resultFile); + } + + private void multiThreadedTestRun( + final IPerfTestRunner runner, final String name, final int threadCount, final String resultFile) + throws Exception { + + final Histogram[] histograms = new Histogram[threadCount]; + for (int i = 0; i < histograms.length; i++) { + histograms[i] = PerfTest.createHistogram(); + } + final int LINES = 256 * 1024; + + final Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threads.length; i++) { + final Histogram histogram = histograms[i]; + threads[i] = new Thread() { + @Override + public void run() { + // int latencyCount = threadCount >= 16 ? 1000000 : 5000000; + final int latencyCount = 5000000; + final int count = PerfTest.throughput ? LINES / threadCount : latencyCount; + runTest(runner, count, "end", histogram, threadCount); + } + }; + } + for (final Thread thread : threads) { + thread.start(); + } + for (final Thread thread : threads) { + thread.join(); + } + + for (final Histogram histogram : histograms) { + PerfTest.reportResult(resultFile, name, histogram); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java index 6bce3125408..bc405d9e673 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -31,7 +31,5 @@ class NoOpIdleStrategy implements IdleStrategy { * @see IdleStrategy */ @Override - public void idle() { - - } + public void idle() {} } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java index 9b34955cfd5..7bfb06c38e1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -21,11 +21,8 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.concurrent.TimeUnit; - 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. *

@@ -35,7 +32,8 @@ */ public class PerfTest { - private static final String LINE100 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456"; + private static final String LINE100 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456"; public static final String LINE500 = LINE100 + LINE100 + LINE100 + LINE100 + LINE100; static boolean verbose = false; @@ -98,8 +96,9 @@ public void doMain(final String[] args) throws Exception { System.exit(0); } - public void runTestAndPrintResult(final IPerfTestRunner runner, final String name, final int threadCount, - final String resultFile) throws Exception { + public void runTestAndPrintResult( + final IPerfTestRunner runner, final String name, final int threadCount, final String resultFile) + throws Exception { final Histogram warmupHist = createHistogram(); // ThreadContext.put("aKey", "mdcVal"); @@ -115,12 +114,13 @@ public void runTestAndPrintResult(final IPerfTestRunner runner, final String nam iterations++; } while (System.nanoTime() - stop < 0); - printf("Warmup complete in %.1f seconds (%d iterations)%n", (System.nanoTime() - t1) - / (1000.0 * 1000.0 * 1000.0), iterations); + printf( + "Warmup complete in %.1f seconds (%d iterations)%n", + (System.nanoTime() - t1) / (1000.0 * 1000.0 * 1000.0), iterations); println("Waiting 10 seconds for buffers to drain warmup data..."); Thread.sleep(3000); - //forceRemap(LINES, iterations, runner); + // forceRemap(LINES, iterations, runner); Thread.sleep(7000); println("Starting the main test..."); @@ -149,8 +149,9 @@ private void forceRemap(final int linesPerIteration, final int iterations, final } while ((todo -= (4096 + LINESEP)) > 0); } - private int runSingleThreadedTest(final IPerfTestRunner runner, final int LINES, final String name, - final String resultFile) throws IOException { + private int runSingleThreadedTest( + final IPerfTestRunner runner, final int LINES, final String name, final String resultFile) + throws IOException { final Histogram latency = createHistogram(); runTest(runner, LINES, "end", latency, 1); reportResult(resultFile, name, latency); @@ -186,7 +187,8 @@ static String createSamplingReport(final String name, final Histogram histogram) if (throughput) { return data.getMax() + " operations/second"; } - final String result = String.format("avg=%.0f 99%%=%d 99.99%%=%d sampleCount=%d", // + final String result = String.format( + "avg=%.0f 99%%=%d 99.99%%=%d sampleCount=%d", // data.getMean(), // data.getTwoNinesUpperBound(), // data.getFourNinesUpperBound(), // @@ -195,8 +197,12 @@ static String createSamplingReport(final String name, final Histogram histogram) return result; } - public void runTest(final IPerfTestRunner runner, final int lines, final String finalMessage, - final Histogram histogram, final int threadCount) { + public void runTest( + final IPerfTestRunner runner, + final int lines, + final String finalMessage, + final Histogram histogram, + final int threadCount) { if (throughput) { runner.runThroughputTest(lines, histogram); } else { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java index 2be64999111..e7620864b9b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java @@ -1,24 +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. */ package org.apache.logging.log4j.core.async.perftest; import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; @@ -27,8 +26,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; - import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.util.Integers; /** * Runs a sequence of performance tests. @@ -36,8 +35,10 @@ public class PerfTestDriver { private static final String DEFAULT_WAIT_STRATEGY = "Block"; - static enum WaitStrategy { - Sleep, Yield, Block; + enum WaitStrategy { + Sleep, + Yield, + Block; public static WaitStrategy get() { return WaitStrategy.valueOf(System.getProperty("WaitStrategy", DEFAULT_WAIT_STRATEGY)); @@ -58,8 +59,15 @@ static class Setup implements Comparable { private final WaitStrategy wait; private final Runner runner; - public Setup(final Class klass, final Runner runner, final String name, final String log4jConfig, - final int threadCount, final WaitStrategy wait, final String... systemProperties) throws IOException { + public Setup( + final Class klass, + final Runner runner, + final String name, + final String log4jConfig, + final int threadCount, + final WaitStrategy wait, + final String... systemProperties) + throws IOException { this.klass = klass; this.runner = runner; this.name = name; @@ -88,7 +96,7 @@ List processArguments(final String java) { args.add("-Dlog4j.configuration=" + log4jConfig); // log4j 1.2 args.add("-Dlog4j.configurationFile=" + log4jConfig); // log4j 2 - args.add("-Dlogback.configurationFile=" + log4jConfig);// logback + args.add("-Dlogback.configurationFile=" + log4jConfig); // logback final int ringBufferSize = getUserSpecifiedRingBufferSize(); if (ringBufferSize >= 128) { @@ -112,7 +120,7 @@ List processArguments(final String java) { private int getUserSpecifiedRingBufferSize() { try { - return Integer.parseInt(System.getProperty("RingBufferSize", "-1")); + return Integers.parseInt(System.getProperty("RingBufferSize", "-1")); } catch (final Exception ignored) { return -1; } @@ -170,7 +178,7 @@ public Stats(final String raw) { average += Long.parseLong(parts[i++].split("=")[1]); pct99 += Long.parseLong(parts[i++].split("=")[1]); pct99_99 += Long.parseLong(parts[i++].split("=")[1]); - count += Integer.parseInt(parts[i].split("=")[1]); + count += Integers.parseInt(parts[i].split("=")[1]); } else { throughputRowCount++; final String number = line.substring(0, line.indexOf(' ')); @@ -184,41 +192,44 @@ public Stats(final String raw) { @Override public String toString() { final String fmt = "throughput: %,d ops/sec. latency(ns): avg=%.1f 99%% < %.1f 99.99%% < %.1f (%d samples)"; - return String.format(fmt, averageOpsPerSec, // + return String.format( + fmt, + averageOpsPerSec, // average / latencyRowCount, // mean latency pct99 / latencyRowCount, // 99% observations less than - pct99_99 / latencyRowCount,// 99.99% observs less than + pct99_99 / latencyRowCount, // 99.99% observs less than count); } } - 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)); + + System.out.printf( + "Done. Total duration: %.1f minutes%n", + (System.nanoTime() - start) / (60.0 * 1000.0 * 1000.0 * 1000.0)); printRanking(tests.toArray(new Setup[tests.size()])); } 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(); @@ -240,7 +251,8 @@ private static List selectTests() throws IOException { // add(tests, 1, "perf6AsyncApndLoc.xml", Runner.Log4j2, "Async Appender includeLocation"); // add(tests, 1, "perf8MixedLoc.xml", Runner.Log4j2, "Mixed sync/async includeLocation"); // add(tests, 1, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation", ALL_ASYNC); - // add(tests, 1, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation CachedClock", ALL_ASYNC, CACHEDCLOCK); + // add(tests, 1, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation CachedClock", + // ALL_ASYNC, CACHEDCLOCK); // add(tests, 1, "perf4PlainLocation.xml", Runner.Log4j2, "Sync includeLocation"); // appenders @@ -265,7 +277,8 @@ private static List selectTests() throws IOException { // add(tests, i, "perf6AsyncApndLoc.xml", Runner.Log4j2, "Async Appender includeLocation"); // add(tests, i, "perf8MixedLoc.xml", Runner.Log4j2, "Mixed sync/async includeLocation"); // add(tests, i, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation", ALL_ASYNC)); - // add(tests, i, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation CachedClock", ALL_ASYNC, CACHEDCLOCK)); + // add(tests, i, "perf4PlainLocation.xml", Runner.Log4j2, "Loggers all async includeLocation CachedClock", + // ALL_ASYNC, CACHEDCLOCK)); // add(tests, i, "perf4PlainLocation.xml", Runner.Log4j2, "Sync includeLocation"); // appenders @@ -277,18 +290,24 @@ private static List selectTests() throws IOException { return tests; } - private static void add(final List tests, final int threadCount, final String config, final Runner runner, final String name, - final String... systemProperties) throws IOException { + private static void add( + final List tests, + final int threadCount, + final String config, + final Runner runner, + final String name, + final String... systemProperties) + throws IOException { final WaitStrategy wait = WaitStrategy.get(); final Class perfTest = threadCount == 1 ? PerfTest.class : MultiThreadPerfTest.class; final Setup setup = new Setup(perfTest, runner, name, config, threadCount, wait, systemProperties); tests.add(setup); } - private static void runPerfTests(final String[] args, final List tests) throws IOException, - InterruptedException, FileNotFoundException { + private static void runPerfTests(final String[] args, final List tests) + throws IOException, InterruptedException { final String java = args.length > 0 ? args[0] : "java"; - final int repeat = args.length > 1 ? Integer.parseInt(args[1]) : 5; + final int repeat = args.length > 1 ? Integers.parseInt(args[1]) : 5; int x = 0; for (final Setup setup : tests) { System.out.print(setup.description()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java index 43069c68978..495110f48ca 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -53,8 +53,7 @@ static class Stats { private final Map> results = new TreeMap<>(); - public PerfTestResultFormatter() { - } + public PerfTestResultFormatter() {} public String format(final String text) throws ParseException { results.clear(); @@ -71,7 +70,11 @@ private String latencyTable() { final char[] tabs = new char[subKeys.size()]; Arrays.fill(tabs, '\t'); final String sep = new String(tabs); - sb.append("\tAverage latency").append(sep).append("99% less than").append(sep).append("99.99% less than"); + sb.append("\tAverage latency") + .append(sep) + .append("99% less than") + .append(sep) + .append("99.99% less than"); sb.append(LF); for (int i = 0; i < 3; i++) { for (final String subKey : subKeys) { @@ -86,15 +89,15 @@ private String latencyTable() { for (final String subKey : sub.keySet()) { final Stats stats = sub.get(subKey); switch (i) { - case 0: - sb.append('\t').append((long) stats.avgLatency); - break; - case 1: - sb.append('\t').append((long) stats.latency99Pct); - break; - case 2: - sb.append('\t').append((long) stats.latency99_99Pct); - break; + case 0: + sb.append('\t').append((long) stats.avgLatency); + break; + case 1: + sb.append('\t').append((long) stats.latency99Pct); + break; + case 2: + sb.append('\t').append((long) stats.latency99_99Pct); + break; } } } @@ -127,15 +130,12 @@ private String throughputTable() { private void process(final String line) throws ParseException { final String key = line.substring(line.indexOf('.') + 1, line.indexOf('(')); final String sub = line.substring(line.indexOf('(') + 1, line.indexOf(')')); - final String throughput = line.substring(line.indexOf("throughput: ") - + "throughput: ".length(), line.indexOf(" ops")); - final String avg = line.substring(line.indexOf("avg=") + "avg=".length(), - line.indexOf(" 99%")); - final String pct99 = line.substring( - line.indexOf("99% < ") + "99% < ".length(), - line.indexOf(" 99.99%")); - final String pct99_99 = line.substring(line.indexOf("99.99% < ") - + "99.99% < ".length(), line.lastIndexOf('(') - 1); + final String throughput = + line.substring(line.indexOf("throughput: ") + "throughput: ".length(), line.indexOf(" ops")); + final String avg = line.substring(line.indexOf("avg=") + "avg=".length(), line.indexOf(" 99%")); + final String pct99 = line.substring(line.indexOf("99% < ") + "99% < ".length(), line.indexOf(" 99.99%")); + final String pct99_99 = + line.substring(line.indexOf("99.99% < ") + "99.99% < ".length(), line.lastIndexOf('(') - 1); final Stats stats = new Stats(throughput, avg, pct99, pct99_99); Map map = results.get(key.trim()); if (map == null) { @@ -151,9 +151,8 @@ private void process(final String line) throws ParseException { private Comparator sort() { return new Comparator() { - List expected = Arrays.asList("1 thread", "2 threads", - "4 threads", "8 threads", "16 threads", "32 threads", - "64 threads"); + final List expected = Arrays.asList( + "1 thread", "2 threads", "4 threads", "8 threads", "16 threads", "32 threads", "64 threads"); @Override public int compare(final String o1, final String o2) { @@ -169,8 +168,7 @@ public int compare(final String o1, final String o2) { public static void main(final String[] args) throws Exception { final PerfTestResultFormatter fmt = new PerfTestResultFormatter(); - final BufferedReader reader = new BufferedReader(new InputStreamReader( - System.in)); + final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null) { fmt.process(line); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java index 93f0f2e4002..c3b9ff9ab39 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -24,12 +24,12 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; - import org.HdrHistogram.Histogram; 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.util.Constants; +import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Loader; /** @@ -81,40 +81,49 @@ * for some concrete visual examples. */ // RUN -// java -XX:+UnlockDiagnosticVMOptions -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution +// java -XX:+UnlockDiagnosticVMOptions -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps +// -XX:+PrintTenuringDistribution // -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:GuaranteedSafepointInterval=500000 // -XX:CompileCommand=dontinline,org.apache.logging.log4j.core.async.perftest.NoOpIdleStrategy::idle -// -cp HdrHistogram-2.1.8.jar:disruptor-3.3.4.jar:log4j-api-2.6-SNAPSHOT.jar:log4j-core-2.6-SNAPSHOT.jar:log4j-core-2.6-SNAPSHOT-tests.jar -// -DAsyncLogger.WaitStrategy=busyspin -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector +// -cp +// HdrHistogram-2.1.8.jar:disruptor-3.3.4.jar:log4j-api-2.6-SNAPSHOT.jar:log4j-core-2.6-SNAPSHOT.jar:log4j-core-2.6-SNAPSHOT-tests.jar +// -DAsyncLogger.WaitStrategy=busyspin +// -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector // -Dlog4j2.enable.threadlocals=true -Dlog4j2.enable.direct.encoders=true // -Xms1G -Xmx1G org.apache.logging.log4j.core.async.perftest.ResponseTimeTest 1 100000 // // RUN recording in Java Flight Recorder: -// %JAVA_HOME%\bin\java -XX:+UnlockCommercialFeatures -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -XX:+FlightRecorder -XX:StartFlightRecording=duration=10m,filename=replayStats-2.6-latency.jfr -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:CompileCommand=dontinline,org.apache.logging.log4j.core.async.perftest.NoOpIdleStrategy::idle -DAsyncLogger.WaitStrategy=yield -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=TRACE -cp .;HdrHistogram-2.1.8.jar;disruptor-3.3.4.jar;log4j-api-2.6-SNAPSHOT.jar;log4j-core-2.6-SNAPSHOT.jar;log4j-core-2.6-SNAPSHOT-tests.jar org.apache.logging.log4j.core.async.perftest.ResponseTimeTest 1 50000 +// %JAVA_HOME%\bin\java -XX:+UnlockCommercialFeatures -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints +// -XX:+FlightRecorder -XX:StartFlightRecording=duration=10m,filename=replayStats-2.6-latency.jfr -verbose:gc +// -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationConcurrentTime +// -XX:+PrintGCApplicationStoppedTime +// -XX:CompileCommand=dontinline,org.apache.logging.log4j.core.async.perftest.NoOpIdleStrategy::idle +// -DAsyncLogger.WaitStrategy=yield -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=TRACE -cp +// .;HdrHistogram-2.1.8.jar;disruptor-3.3.4.jar;log4j-api-2.6-SNAPSHOT.jar;log4j-core-2.6-SNAPSHOT.jar;log4j-core-2.6-SNAPSHOT-tests.jar org.apache.logging.log4j.core.async.perftest.ResponseTimeTest 1 50000 public class ResponseTimeTest { private static final String LATENCY_MSG = new String(new char[64]); public static void main(final String[] args) throws Exception { if (args.length < 2) { - System.out.println("Please specify thread count, target throughput (msg/sec) " + - "and logger library (Log4j1, Log4j2, Logback, JUL)"); + System.out.println("Please specify thread count, target throughput (msg/sec) " + + "and logger library (Log4j1, Log4j2, Logback, JUL)"); return; } - final int threadCount = Integer.parseInt(args[0]); + final int threadCount = Integers.parseInt(args[0]); final double loadMessagesPerSec = Double.parseDouble(args[1]); 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"); + // System.setProperty("Log4jContextSelector", AsyncLoggerContextSelector.class.getName()); + // System.setProperty("log4j.configurationFile", "perf3PlainNoLoc.xml"); if (System.getProperty("AsyncLogger.WaitStrategy") == null) { System.setProperty("AsyncLogger.WaitStrategy", "Yield"); } - //for (Object key : System.getProperties().keySet()) { + // for (Object key : System.getProperties().keySet()) { // System.out.println(key + "=" + System.getProperty((String) key)); - //} + // } // initialize the logger final String wrapper = loggerLib.startsWith("Run") ? loggerLib : "Run" + loggerLib; @@ -128,8 +137,12 @@ public static void main(final String[] args) throws Exception { ? new NoOpIdleStrategy() : new YieldIdleStrategy(); - System.out.printf("%s: %d threads, load is %,f msg/sec, using %s%n", loggerLib, threadCount, - loadMessagesPerSec, idleStrategy.getClass().getSimpleName()); + System.out.printf( + "%s: %d threads, load is %,f msg/sec, using %s%n", + loggerLib, + threadCount, + loadMessagesPerSec, + idleStrategy.getClass().getSimpleName()); // Warmup: run as many iterations of 50,000 calls to logger.log as we can in 1 minute final long WARMUP_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(1); @@ -137,12 +150,19 @@ public static void main(final String[] args) throws Exception { final List warmupResponseTmHistograms = new ArrayList<>(threadCount); final int WARMUP_COUNT = 50000 / threadCount; - runLatencyTest(logger, WARMUP_DURATION_MILLIS, WARMUP_COUNT, loadMessagesPerSec, idleStrategy, - warmupServiceTmHistograms, warmupResponseTmHistograms, threadCount); + 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) { - //System.gc(); - //Thread.sleep(5000); + // System.gc(); + // Thread.sleep(5000); } System.out.println("-----------------Starting measured run. load=" + loadMessagesPerSec); @@ -154,8 +174,15 @@ public static void main(final String[] args) throws Exception { // Actual test: run as many iterations of 1,000,000 calls to logger.log as we can in 4 minutes. final long TEST_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(4); final int COUNT = (1000 * 1000) / threadCount; - runLatencyTest(logger, TEST_DURATION_MILLIS, COUNT, loadMessagesPerSec, idleStrategy, serviceTmHistograms, - responseTmHistograms, threadCount); + runLatencyTest( + logger, + TEST_DURATION_MILLIS, + COUNT, + loadMessagesPerSec, + idleStrategy, + serviceTmHistograms, + responseTmHistograms, + threadCount); logger.shutdown(); final long end = System.currentTimeMillis(); @@ -168,14 +195,16 @@ public static void main(final String[] args) throws Exception { resultResponseTm.outputPercentileDistribution(System.out, 1000.0); writeToFile("r", resultResponseTm, (int) (loadMessagesPerSec / 1000), 1000.0); - System.out.printf("%n%s: %d threads, load %,f msg/sec, ringbuffer full=%d%n", loggerLib, threadCount, - loadMessagesPerSec, PrintingAsyncQueueFullPolicy.ringbufferFull.get()); + System.out.printf( + "%n%s: %d threads, load %,f msg/sec, ringbuffer full=%d%n", + loggerLib, threadCount, loadMessagesPerSec, PrintingAsyncQueueFullPolicy.ringbufferFull.get()); System.out.println("Test duration: " + (end - start) / 1000.0 + " seconds"); } - private static void writeToFile(final String suffix, final Histogram hist, final int thousandMsgPerSec, - final double scale) throws IOException { - try (PrintStream pout = new PrintStream(new FileOutputStream(thousandMsgPerSec + "k" + suffix))) { + private static void writeToFile( + final String suffix, final Histogram hist, final int thousandMsgPerSec, final double scale) + throws IOException { + try (final PrintStream pout = new PrintStream(new FileOutputStream(thousandMsgPerSec + "k" + suffix))) { hist.outputPercentileDistribution(pout, scale); } } @@ -190,10 +219,16 @@ private static Histogram createResultHistogram(final List list, final return result; } - public static void runLatencyTest(final IPerfTestRunner logger, final long durationMillis, final int samples, - final double loadMessagesPerSec, final IdleStrategy idleStrategy, - final List serviceTmHistograms, final List responseTmHistograms, - final int threadCount) throws InterruptedException { + public static void runLatencyTest( + final IPerfTestRunner logger, + final long durationMillis, + final int samples, + final double loadMessagesPerSec, + final IdleStrategy idleStrategy, + final List serviceTmHistograms, + final List responseTmHistograms, + final int threadCount) + throws InterruptedException { final Thread[] threads = new Thread[threadCount]; final CountDownLatch LATCH = new CountDownLatch(threadCount); @@ -227,8 +262,12 @@ public void run() { } } - private static void runLatencyTest(final int samples, final IPerfTestRunner logger, final Histogram serviceTmHist, - final Histogram responseTmHist, final Pacer pacer) { + private static void runLatencyTest( + final int samples, + final IPerfTestRunner logger, + final Histogram serviceTmHist, + final Histogram responseTmHist, + final Pacer pacer) { for (int i = 0; i < samples; i++) { final long expectedStartTimeNanos = pacer.expectedNextOperationNanoTime(); @@ -338,11 +377,10 @@ public long nsecToNextOperation() { } // Figure out if it's time to send, per catch up throughput: - final long unitsCompletedSinceCatchUpStart = - unitsCompleted - unitsCompletedAtCatchUpStart; + final long unitsCompletedSinceCatchUpStart = unitsCompleted - unitsCompletedAtCatchUpStart; - nextStartTime = catchUpStartTime + - (long) (unitsCompletedSinceCatchUpStart / catchUpThroughputInUnitsPerNsec); + nextStartTime = + catchUpStartTime + (long) (unitsCompletedSinceCatchUpStart / catchUpThroughputInUnitsPerNsec); if (nextStartTime > now) { // Not yet time to send, even at catch-up throughout: diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java new file mode 100644 index 00000000000..a5ce8bc87b8 --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.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.async.perftest; + +import com.conversantmedia.util.concurrent.SpinPolicy; +import java.util.concurrent.BlockingQueue; +import org.apache.logging.log4j.core.async.DisruptorBlockingQueueFactory; + +public class RunConversant extends AbstractRunQueue { + + @Override + BlockingQueue createQueue(final int capacity) { + return DisruptorBlockingQueueFactory.createFactory(SpinPolicy.SPINNING) + .create(capacity); + } +} diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java new file mode 100644 index 00000000000..d84d86d86ae --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.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.async.perftest; + +import java.util.concurrent.BlockingQueue; +import org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory; +import org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory.WaitStrategy; + +public class RunJCTools extends AbstractRunQueue { + + @Override + BlockingQueue createQueue(final int capacity) { + return JCToolsBlockingQueueFactory.createFactory(WaitStrategy.SPIN) + .create(capacity); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java index 6db549e6ecc..db5a0987c0b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java @@ -1,26 +1,24 @@ /* * 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.logging.log4j.core.async.perftest; 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()); @@ -38,8 +36,8 @@ public void runThroughputTest(final int lines, final Histogram histogram) { } @Override - public void runLatencyTest(final int samples, final Histogram histogram, - final long nanoTimeCost, final int threadCount) { + public void runLatencyTest( + final int samples, final Histogram histogram, final long nanoTimeCost, final int threadCount) { final Logger logger = LOGGER; for (int i = 0; i < samples; i++) { final long s1 = System.nanoTime(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java index df2a15d5d41..2f49e4eef02 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java @@ -1,25 +1,24 @@ /* * 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.logging.log4j.core.async.perftest; 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()); @@ -36,10 +35,9 @@ public void runThroughputTest(final int lines, final Histogram histogram) { histogram.addObservation(opsPerSec); } - @Override - public void runLatencyTest(final int samples, final Histogram histogram, - final long nanoTimeCost, final int threadCount) { + public void runLatencyTest( + final int samples, final Histogram histogram, final long nanoTimeCost, final int threadCount) { final Logger logger = LOGGER; for (int i = 0; i < samples; i++) { final long s1 = System.nanoTime(); @@ -58,13 +56,11 @@ public void runLatencyTest(final int samples, final Histogram histogram, } } - @Override public void shutdown() { CoreLoggerContexts.stopLoggerContext(); // stop async thread } - @Override public void log(final String finalMessage) { LOGGER.info(finalMessage); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java index aaf22c6239c..9ab0c25cd14 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java @@ -1,27 +1,24 @@ /* * 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.logging.log4j.core.async.perftest; -import org.slf4j.LoggerFactory; - import ch.qos.logback.classic.Logger; import ch.qos.logback.core.spi.LifeCycle; - -import com.lmax.disruptor.collections.Histogram; +import org.slf4j.LoggerFactory; public class RunLogback implements IPerfTestRunner { final Logger LOGGER = (Logger) LoggerFactory.getLogger(getClass()); @@ -39,8 +36,8 @@ public void runThroughputTest(final int lines, final Histogram histogram) { } @Override - public void runLatencyTest(final int samples, final Histogram histogram, - final long nanoTimeCost, final int threadCount) { + public void runLatencyTest( + final int samples, final Histogram histogram, final long nanoTimeCost, final int threadCount) { final Logger logger = LOGGER; for (int i = 0; i < samples; i++) { final long s1 = System.nanoTime(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java index dd4f82a5caf..d1de5621d28 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async.perftest; @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.List; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.async.AsyncLogger; @@ -80,7 +79,9 @@ public static void main(final String[] args) throws Exception { printReport("Test", UPTIMES, DURATIONS, warmupCount, COUNT); final StringBuilder sb = new StringBuilder(512); - sb.append("Test took: ").append(testDurationNanos/(1000.0*1000.0*1000.0)).append(" sec"); + sb.append("Test took: ") + .append(testDurationNanos / (1000.0 * 1000.0 * 1000.0)) + .append(" sec"); System.out.println(sb); final List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); @@ -94,8 +95,8 @@ public static void main(final String[] args) throws Exception { } } - private static void printReport(final String label, final long[] UPTIMES, final long[] DURATIONS, - final int offset, final int length) { + private static void printReport( + final String label, final long[] UPTIMES, final long[] DURATIONS, final int offset, final int length) { final StringBuilder sb = new StringBuilder(512); long total = 0; for (int i = offset; i < offset + length; i++) { @@ -107,15 +108,23 @@ private static void printReport(final String label, final long[] UPTIMES, final System.out.println(sb); } sb.setLength(0); - sb.append("Average ").append(label).append(" throughput: ").append(total/length).append(" ops/s"); + sb.append("Average ") + .append(label) + .append(" throughput: ") + .append(total / length) + .append(" ops/s"); System.out.println(sb); sb.setLength(0); System.out.println(sb.append(label).append(" ran: ").append(length).append(" iterations")); } - private static void runTest(final Logger logger, final RuntimeMXBean runtimeMXBean, final long[] UPTIMES, - final long[] DURATIONS, final int index) { + private static void runTest( + final Logger logger, + final RuntimeMXBean runtimeMXBean, + final long[] UPTIMES, + final long[] DURATIONS, + final int index) { UPTIMES[index] = runtimeMXBean.getUptime(); final long startNanos = System.nanoTime(); loop(logger, ITERATIONS); @@ -124,12 +133,12 @@ private static void runTest(final Logger logger, final RuntimeMXBean runtimeMXBe } private static void loop(final Logger logger, final int iterations) { -// String[] arg7 = new String[] {"arg1", "arg2","arg3", "arg4","arg5", "arg6","arg7", }; -// String[] arg2 = new String[] {"arg1", "arg2", }; -// - for (int i = 0; i < iterations; i ++) { -// logger.error("7 arg message {} {} {} {} {} {} {}"); -// logger.error("7 arg message {} {} "); + // String[] arg7 = new String[] {"arg1", "arg2","arg3", "arg4","arg5", "arg6","arg7", }; + // String[] arg2 = new String[] {"arg1", "arg2", }; + // + for (int i = 0; i < iterations; i++) { + // logger.error("7 arg message {} {} {} {} {} {} {}"); + // logger.error("7 arg message {} {} "); logger.error("simple text message"); } @@ -139,9 +148,9 @@ private static void workAroundLog4j2_5Bug() { // use reflection so we can use the same test with older versions of log4j2 try { final Method setUseThreadLocals = - AsyncLoggerContext.class.getDeclaredMethod("setUseThreadLocals", new Class[]{boolean.class}); + AsyncLoggerContext.class.getDeclaredMethod("setUseThreadLocals", boolean.class); final LoggerContext context = LogManager.getContext(false); - setUseThreadLocals.invoke(context, new Object[] {Boolean.TRUE}); + setUseThreadLocals.invoke(context, Boolean.TRUE); } catch (final Throwable ignored) { } } diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java new file mode 100644 index 00000000000..540b7fee562 --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.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.async.perftest; + +/** + * Idle strategy that yields the thread. + */ +class YieldIdleStrategy implements IdleStrategy { + + /** + * yields the current thread. + * @see IdleStrategy + */ + @Override + public void idle() { + Thread.yield(); + } +} diff --git a/log4j-core-its/src/test/resources/logback-test.xml b/log4j-core-its/src/test/resources/logback-test.xml index 989bea65c8c..88f97346ada 100644 --- a/log4j-core-its/src/test/resources/logback-test.xml +++ b/log4j-core-its/src/test/resources/logback-test.xml @@ -1,19 +1,19 @@ diff --git a/log4j-core-java9/.gitignore b/log4j-core-java9/.gitignore new file mode 100644 index 00000000000..ae3c1726048 --- /dev/null +++ b/log4j-core-java9/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/log4j-core-java9/pom.xml b/log4j-core-java9/pom.xml index b46f8c91044..2cb3c3d07a2 100644 --- a/log4j-core-java9/pom.xml +++ b/log4j-core-java9/pom.xml @@ -1,36 +1,36 @@ 4.0.0 org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent log4j-core-java9 pom Apache Log4j Implementation Java 9 support The Apache Log4j Implementation (Java 9) - ${basedir}/.. - Log4j Implementation Documentation - /core + 9 + true + true @@ -39,9 +39,12 @@ log4j-api - junit - junit - test + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml org.apache.maven @@ -51,83 +54,15 @@ - - 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 + package log4j-core-java9-${project.version} false @@ -138,15 +73,20 @@ + org.apache.maven.plugins - maven-deploy-plugin - ${deploy.plugin.version} - - true - + maven-compiler-plugin + + + default-compile + + compile + + compile + + - diff --git a/log4j-core-java9/src/assembly/java9.xml b/log4j-core-java9/src/assembly/java9.xml index f8f129dc0d0..4349d38f147 100644 --- a/log4j-core-java9/src/assembly/java9.xml +++ b/log4j-core-java9/src/assembly/java9.xml @@ -1,23 +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. + --> src @@ -29,22 +26,11 @@ ${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 + org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.class + org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.class + org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.class + org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.class + org/apache/logging/log4j/core/util/internal/UnsafeUtil*.class diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java new file mode 100644 index 00000000000..61faeb9e819 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.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.impl; + +import java.io.Serializable; +import org.apache.logging.log4j.core.pattern.TextRenderer; + +/** + * Dummy class to let ExtendedStackTracElement to compile. It will not be copied + * to `log4j-core`. + */ +public class ExtendedClassInfo implements Serializable { + + public ExtendedClassInfo(final boolean exact, final String location, final String version) {} + + public boolean getExact() { + return false; + } + + public String getLocation() { + return null; + } + + public String getVersion() { + return null; + } + + public void renderOn(final StringBuilder output, final TextRenderer textRenderer) {} +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java new file mode 100644 index 00000000000..6b0a130b274 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Serializable; +import java.util.Objects; +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. + *

+ * Complements a StackTraceElement with: + *

+ *
    + *
  • exact: whether the class was obtained via {@link sun.reflect.Reflection#getCallerClass(int)}
  • + *
  • location: a classpath element or a jar
  • + *
  • version
  • + *
+ */ +public final class ExtendedStackTraceElement implements Serializable { + + static final ExtendedStackTraceElement[] EMPTY_ARRAY = {}; + + private static final long serialVersionUID = -2171069569241280505L; + ; + + private final ExtendedClassInfo extraClassInfo; + + private final StackTraceElement stackTraceElement; + + public ExtendedStackTraceElement( + final StackTraceElement stackTraceElement, final ExtendedClassInfo extraClassInfo) { + this.stackTraceElement = stackTraceElement; + this.extraClassInfo = extraClassInfo; + } + + /** + * 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) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ExtendedStackTraceElement)) { + return false; + } + final ExtendedStackTraceElement other = (ExtendedStackTraceElement) obj; + if (!Objects.equals(this.extraClassInfo, other.extraClassInfo)) { + return false; + } + if (!Objects.equals(this.stackTraceElement, other.stackTraceElement)) { + return false; + } + 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(); + } + + public boolean getExact() { + return this.extraClassInfo.getExact(); + } + + public ExtendedClassInfo getExtraClassInfo() { + return this.extraClassInfo; + } + + public String getFileName() { + return this.stackTraceElement.getFileName(); + } + + public int getLineNumber() { + return this.stackTraceElement.getLineNumber(); + } + + public String getLocation() { + return this.extraClassInfo.getLocation(); + } + + public String getMethodName() { + return this.stackTraceElement.getMethodName(); + } + + public StackTraceElement getStackTraceElement() { + return this.stackTraceElement; + } + + public String getVersion() { + return this.extraClassInfo.getVersion(); + } + + @Override + public int hashCode() { + return Objects.hash(extraClassInfo, stackTraceElement); + } + + public boolean isNativeMethod() { + return this.stackTraceElement.isNativeMethod(); + } + + void renderOn(final StringBuilder output, final TextRenderer textRenderer) { + render(this.stackTraceElement, output, textRenderer); + textRenderer.render(" ", output, "Text"); + this.extraClassInfo.renderOn(output, textRenderer); + } + + private void render( + final StackTraceElement stElement, final StringBuilder output, final TextRenderer textRenderer) { + final String classLoaderName = getClassLoaderName(); + if (Strings.isNotEmpty(classLoaderName)) { + switch (classLoaderName) { + case "app": + case "boot": + case "platform": + break; + default: + textRenderer.render(classLoaderName, output, "StackTraceElement.ClassLoaderName"); + textRenderer.render("/", output, "StackTraceElement.ClassLoaderSeparator"); + } + } + 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"); + if (stElement.isNativeMethod()) { + textRenderer.render("(Native Method)", output, "StackTraceElement.NativeMethod"); + } else if (fileName != null && lineNumber >= 0) { + textRenderer.render("(", output, "StackTraceElement.Container"); + textRenderer.render(fileName, output, "StackTraceElement.FileName"); + textRenderer.render(":", output, "StackTraceElement.ContainerSeparator"); + textRenderer.render(Integer.toString(lineNumber), output, "StackTraceElement.LineNumber"); + textRenderer.render(")", output, "StackTraceElement.Container"); + } else if (fileName != null) { + textRenderer.render("(", output, "StackTraceElement.Container"); + textRenderer.render(fileName, output, "StackTraceElement.FileName"); + textRenderer.render(")", output, "StackTraceElement.Container"); + } else { + textRenderer.render("(", output, "StackTraceElement.Container"); + textRenderer.render("Unknown Source", output, "StackTraceElement.UnknownSource"); + textRenderer.render(")", output, "StackTraceElement.Container"); + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + renderOn(sb, PlainTextRenderer.getInstance()); + return sb.toString(); + } +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java new file mode 100644 index 00000000000..8b5575d70a3 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java @@ -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. + */ +package org.apache.logging.log4j.core.jackson; + +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; +import java.io.Serializable; +import org.apache.logging.log4j.core.impl.ExtendedClassInfo; +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; + +/** + * Mix-in for {@link ExtendedStackTraceElement}. + */ +@JsonPropertyOrder({ + // @formatter:off + ExtendedStackTraceElementMixIn.ATTR_CLASS_LOADER_NAME, + ExtendedStackTraceElementMixIn.ATTR_MODULE, + ExtendedStackTraceElementMixIn.ATTR_MODULE_VERSION, + ExtendedStackTraceElementMixIn.ATTR_CLASS, + ExtendedStackTraceElementMixIn.ATTR_METHOD, + ExtendedStackTraceElementMixIn.ATTR_FILE, + ExtendedStackTraceElementMixIn.ATTR_LINE, + ExtendedStackTraceElementMixIn.ATTR_EXACT, + ExtendedStackTraceElementMixIn.ATTR_LOCATION, + ExtendedStackTraceElementMixIn.ATTR_VERSION + // @formatter:on +}) +abstract class ExtendedStackTraceElementMixIn implements Serializable { + + protected static final String ATTR_CLASS_LOADER_NAME = StackTraceElementConstants.ATTR_CLASS_LOADER_NAME; + protected static final String ATTR_MODULE = StackTraceElementConstants.ATTR_MODULE; + protected static final String ATTR_MODULE_VERSION = StackTraceElementConstants.ATTR_MODULE_VERSION; + protected static final String ATTR_CLASS = StackTraceElementConstants.ATTR_CLASS; + protected static final String ATTR_METHOD = StackTraceElementConstants.ATTR_METHOD; + protected static final String ATTR_FILE = StackTraceElementConstants.ATTR_FILE; + protected static final String ATTR_LINE = StackTraceElementConstants.ATTR_LINE; + protected static final String ATTR_EXACT = "exact"; + protected static final String ATTR_LOCATION = "location"; + protected static final String ATTR_VERSION = "version"; + private static final long serialVersionUID = 1L; + + @JsonCreator + public ExtendedStackTraceElementMixIn( + // @formatter:off + @JsonProperty(ATTR_CLASS_LOADER_NAME) final String classLoaderName, + @JsonProperty(ATTR_MODULE) final String moduleName, + @JsonProperty(ATTR_MODULE_VERSION) final String moduleVersion, + @JsonProperty(ATTR_CLASS) final String declaringClass, + @JsonProperty(ATTR_METHOD) final String methodName, + @JsonProperty(ATTR_FILE) final String fileName, + @JsonProperty(ATTR_LINE) final int lineNumber, + @JsonProperty(ATTR_EXACT) final boolean exact, + @JsonProperty(ATTR_LOCATION) final String location, + @JsonProperty(ATTR_VERSION) final String version + // @formatter:on + ) { + // empty + } + + @JsonProperty(ATTR_CLASS_LOADER_NAME) + @JacksonXmlProperty(localName = ATTR_CLASS_LOADER_NAME, isAttribute = true) + public abstract String getClassLoaderName(); + + @JsonProperty(ATTR_MODULE) + @JacksonXmlProperty(localName = ATTR_MODULE, isAttribute = true) + public abstract String getModuleName(); + + @JsonProperty(ATTR_MODULE_VERSION) + @JacksonXmlProperty(localName = ATTR_MODULE_VERSION, isAttribute = true) + public abstract String getModuleVersion(); + + @JsonProperty(ATTR_CLASS) + @JacksonXmlProperty(localName = ATTR_CLASS, isAttribute = true) + public abstract String getClassName(); + + @JsonProperty(ATTR_EXACT) + @JacksonXmlProperty(localName = ATTR_EXACT, isAttribute = true) + public abstract boolean getExact(); + + @JsonIgnore + public abstract ExtendedClassInfo getExtraClassInfo(); + + @JsonProperty(ATTR_FILE) + @JacksonXmlProperty(localName = ATTR_FILE, isAttribute = true) + public abstract String getFileName(); + + @JsonProperty(ATTR_LINE) + @JacksonXmlProperty(localName = ATTR_LINE, isAttribute = true) + public abstract int getLineNumber(); + + @JsonProperty(ATTR_LOCATION) + @JacksonXmlProperty(localName = ATTR_LOCATION, isAttribute = true) + public abstract String getLocation(); + + @JsonProperty(ATTR_METHOD) + @JacksonXmlProperty(localName = ATTR_METHOD, isAttribute = true) + public abstract String getMethodName(); + + @JsonIgnore + abstract StackTraceElement getStackTraceElement(); + + @JsonProperty(ATTR_VERSION) + @JacksonXmlProperty(localName = ATTR_VERSION, isAttribute = true) + public abstract String getVersion(); + + @JsonIgnore + public abstract boolean isNativeMethod(); +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.java new file mode 100644 index 00000000000..75b4b4b4afa --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.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.jackson; + +import com.fasterxml.jackson.core.JsonParser; +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; +import com.fasterxml.jackson.databind.util.ClassUtil; +import java.io.IOException; +import org.apache.logging.log4j.core.util.Integers; + +/** + * Used by Jackson to deserialize a {@link StackTraceElement}. Serialization is + * performed by {@link StackTraceElementMixIn}. + *

+ * 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 { + JsonToken t = jp.getCurrentToken(); + // Must get an Object + if (t == JsonToken.START_OBJECT) { + String classLoaderName = null, + moduleName = null, + moduleVersion = null, + className = null, + methodName = null, + fileName = null; + int lineNumber = -1; + + while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { + final String propName = jp.getCurrentName(); + switch (propName) { + case StackTraceElementConstants.ATTR_CLASS: { + className = jp.getText(); + break; + } + case StackTraceElementConstants.ATTR_FILE: { + fileName = jp.getText(); + break; + } + case StackTraceElementConstants.ATTR_LINE: { + 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 = Integers.parseInt(jp.getText()); + } catch (final NumberFormatException e) { + throw JsonMappingException.from( + jp, "Non-numeric token (" + t + ") for property 'line'", e); + } + } + break; + } + case StackTraceElementConstants.ATTR_METHOD: { + methodName = jp.getText(); + break; + } + case "nativeMethod": { + // no setter, not passed via constructor: ignore + break; + } + case StackTraceElementConstants.ATTR_CLASS_LOADER_NAME: { + classLoaderName = jp.getText(); + break; + } + case StackTraceElementConstants.ATTR_MODULE: { + moduleName = jp.getText(); + break; + } + case StackTraceElementConstants.ATTR_MODULE_VERSION: { + moduleVersion = jp.getText(); + break; + } + default: { + this.handleUnknownProperty(jp, ctxt, this._valueClass, propName); + } + } + } + return new StackTraceElement( + classLoaderName, moduleName, moduleVersion, className, methodName, fileName, lineNumber); + } + throw JsonMappingException.from( + jp, + String.format( + "Cannot deserialize instance of %s out of %s token", ClassUtil.nameOf(this._valueClass), t)); + } +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementConstants.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementConstants.java new file mode 100644 index 00000000000..b54a9bc33ed --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementConstants.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.jackson; + +/** + * Defines constants use for naming stack trace elements. + */ +public class StackTraceElementConstants { + + public static final String ATTR_CLASS_LOADER_NAME = "classLoaderName"; + public static final String ATTR_MODULE = "module"; + public static final String ATTR_MODULE_VERSION = "moduleVersion"; + public static final String ATTR_CLASS = "class"; + public static final String ATTR_FILE = "file"; + public static final String ATTR_LINE = "line"; + public static final String ATTR_METHOD = "method"; +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.java new file mode 100644 index 00000000000..c7cd1eb5c45 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.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.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 used to serialize a {@link StackTraceElement}. Deserialization + * is performed by {@link StackTraceElementMixIn}. + *

+ * Consider this class private. + *

+ * + * @see StackTraceElement + */ +@JsonIgnoreProperties("nativeMethod") +abstract class StackTraceElementMixIn { + @JsonCreator + StackTraceElementMixIn( + // @formatter:off + @JsonProperty(StackTraceElementConstants.ATTR_CLASS_LOADER_NAME) final String classLoaderName, + @JsonProperty(StackTraceElementConstants.ATTR_MODULE) final String moduleName, + @JsonProperty(StackTraceElementConstants.ATTR_MODULE_VERSION) final String moduleVersion, + @JsonProperty(StackTraceElementConstants.ATTR_CLASS) final String declaringClass, + @JsonProperty(StackTraceElementConstants.ATTR_METHOD) final String methodName, + @JsonProperty(StackTraceElementConstants.ATTR_FILE) final String fileName, + @JsonProperty(StackTraceElementConstants.ATTR_LINE) final int lineNumber) + // @formatter:on + { + // empty + } + + @JsonProperty(StackTraceElementConstants.ATTR_CLASS_LOADER_NAME) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_CLASS_LOADER_NAME, isAttribute = true) + abstract String getClassLoaderName(); + + @JsonProperty(StackTraceElementConstants.ATTR_MODULE) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_MODULE, isAttribute = true) + abstract String getModuleName(); + + @JsonProperty(StackTraceElementConstants.ATTR_MODULE_VERSION) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_MODULE_VERSION, isAttribute = true) + abstract String getModuleVersion(); + + @JsonProperty(StackTraceElementConstants.ATTR_CLASS) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_CLASS, isAttribute = true) + abstract String getClassName(); + + @JsonProperty(StackTraceElementConstants.ATTR_FILE) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_FILE, isAttribute = true) + abstract String getFileName(); + + @JsonProperty(StackTraceElementConstants.ATTR_LINE) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_LINE, isAttribute = true) + abstract int getLineNumber(); + + @JsonProperty(StackTraceElementConstants.ATTR_METHOD) + @JacksonXmlProperty(localName = StackTraceElementConstants.ATTR_METHOD, isAttribute = true) + abstract String getMethodName(); +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java new file mode 100644 index 00000000000..a1c187bba32 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.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.pattern; + +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; + +/** + * Dummy class to allow {@link ExtendedStackTraceElement} to compile. It will + * not be copied to `log4j-core`. + */ +public abstract class PlainTextRenderer implements TextRenderer { + + public static PlainTextRenderer getInstance() { + return null; + } +} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java new file mode 100644 index 00000000000..d7983969e64 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.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.pattern; + +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; + +/** + * Dummy class to allow {@link ExtendedStackTraceElement} to compile. It will + * not be copied to `log4j-core`. + */ +public interface TextRenderer { + + void render(String input, StringBuilder output, String styleName); + + void render(StringBuilder input, StringBuilder output); +} 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/Integers.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Integers.java new file mode 100644 index 00000000000..8dbb6320a99 --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Integers.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.util; + +/** + * Dummy class used for compilation. It will not be copied to `log4j-core`. + */ +public class Integers { + + public static int parseInt(String value) { + return 0; + } +} 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/main/java/org/apache/logging/log4j/core/util/internal/UnsafeUtil.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/internal/UnsafeUtil.java new file mode 100644 index 00000000000..0380cd7b3ed --- /dev/null +++ b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/internal/UnsafeUtil.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.util.internal; + +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import sun.misc.Unsafe; + +/** + * Provides access to unsafe operations. + */ +public class UnsafeUtil { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Unsafe unsafe = findUnsafe(); + + private static Unsafe findUnsafe() { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public Unsafe run() throws ReflectiveOperationException, SecurityException { + final Field unsafeField = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + return (Unsafe) unsafeField.get(null); + } + }); + } catch (PrivilegedActionException e) { + final Exception wrapped = e.getException(); + if (wrapped instanceof SecurityException) { + throw (SecurityException) wrapped; + } + LOGGER.warn("sun.misc.Unsafe is not available. This will impact memory usage.", e); + } + return null; + } + + public static void clean(final ByteBuffer bb) throws Exception { + if (unsafe != null && bb.isDirect()) { + unsafe.invokeCleaner(bb); + } + } +} 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 index 6bffac68c33..047439d5d33 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.util; @@ -20,5 +20,4 @@ * 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 { -} +public class Dummy {} diff --git a/log4j-core-test/.log4j-plugin-processing-activator b/log4j-core-test/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-core-test/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml new file mode 100644 index 00000000000..8c8cbc19bbd --- /dev/null +++ b/log4j-core-test/pom.xml @@ -0,0 +1,492 @@ + + + + + 4.0.0 + + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + + log4j-core-test + + Apache Log4j Core Tests + The Apache Log4j Implementation Tests + + + true + + 9 + + + org.apache.logging.log4j.core.test + + + org.junit.*;resolution:=optional, + org.hamcrest.*;resolution:=optional, + org.junitpioneer.*;resolution:=optional, + com.google.monitoring.*;resolution:=optional, + javax.tools;resolution:=optional, + org.assertj.*;resolution:=optional, + org.awaitility.*;resolution:=optional, + org.springframework.mock.*;resolution:=optional + + + + junit;transitive=false, + org.hamcrest;transitive=false, + org.junit.jupiter.api;transitive=false, + + java.allocation.instrumenter;substitute="java-allocation-instrumenter", + spring.test;substitute="spring-test" + + + + 4.0.0 + 2.40.1 + + + + + + org.apache.logging.log4j + log4j-api-test + + + + org.apache.logging.log4j + log4j-core + + + + org.assertj + assertj-core + + + + org.awaitility + awaitility + + + + commons-io + commons-io + + + + org.apache.commons + commons-lang3 + + + + org.hamcrest + hamcrest + + + + + com.google.code.java-allocation-instrumenter + java-allocation-instrumenter + + + + junit + junit + + + + org.junit.jupiter + junit-jupiter-api + + + + org.junit.platform + junit-platform-commons + + + + + org.springframework + spring-test + + + + + org.apache.activemq + activemq-broker + test + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + + + + + + org.apache-extras.beanshell + bsh + test + + + + + commons-codec + commons-codec + test + + + + + org.apache.commons + commons-compress + test + + + + org.apache.commons + commons-csv + test + + + + commons-logging + commons-logging + test + + + + + com.conversantmedia + disruptor + test + + + + + com.lmax + disruptor + test + + + + org.zapodot + embedded-ldap-junit + test + + + + org.apache.groovy + groovy-dateutil + test + + + + org.apache.groovy + groovy-jsr223 + test + + + + com.h2database + h2 + test + + + + + org.hsqldb + hsqldb + jdk8 + test + + + + + com.fasterxml.jackson.core + jackson-databind + test + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + test + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + test + + + + + javax.jms + javax.jms-api + test + + + + org.jspecify + jspecify + test + + + com.sun.mail + javax.mail + test + + + + javax.mail + javax.mail-api + test + + + + + org.jctools + jctools-core + test + + + + + org.zeromq + jeromq + test + + + + + org.jmdns + jmdns + test + + + + + net.javacrumbs.json-unit + json-unit-assertj + ${json-unit.version} + test + + + + net.javacrumbs.json-unit + json-unit + ${json-unit.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + + org.junit-pioneer + junit-pioneer + test + + + + + org.junit.vintage + junit-vintage-engine + test + + + + + org.apache.kafka + kafka-clients + test + + + + org.apache.maven + maven-core + test + + + + + org.mockito + mockito-core + test + + + + org.mockito + mockito-junit-jupiter + test + + + + org.codehaus.plexus + plexus-utils + test + + + + + com.github.tomakehurst + wiremock-jre8 + test + + + + org.xmlunit + xmlunit-core + test + + + + org.xmlunit + xmlunit-matchers + test + + + + + org.tukaani + xz + test + + + + com.github.luben + zstd-jni + test + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test-disruptor-4 + + test + + + + + com.lmax + disruptor + ${disruptor4.version} + + + + com.lmax:disruptor + + org.apache.logging.log4j.core.test.categories.AsyncLoggers + + + + + + + + + + + + + + java8-incompat-fixes + + + + + !env.CI + + + + + + + org.openjdk.nashorn + nashorn-core + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.net=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + + + + + + + + + + disruptor-4 + + + com.lmax + disruptor + ${disruptor4.version} + + + + + + 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 90% 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..ffd7d0b2769 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.test; +package org.apache.logging.log4j.core.test; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.DatagramSocket; import java.net.ServerSocket; @@ -28,6 +29,7 @@ /** * Finds currently available server ports. */ +@SuppressFBWarnings("UNENCRYPTED_SERVER_SOCKET") public final class AvailablePortFinder { /** @@ -53,7 +55,7 @@ public final class AvailablePortFinder { /** * Incremented to the next lowest available port when getNextAvailable() is called. */ - private static AtomicInteger currentMinPort = new AtomicInteger(MIN_PORT_NUMBER); + private static final AtomicInteger currentMinPort = new AtomicInteger(MIN_PORT_NUMBER); /** * Creates a new instance. @@ -151,5 +153,4 @@ public static synchronized boolean available(final int port) throws IllegalArgum return false; } - } 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..45416c79242 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortSystemPropertyTestRule.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.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 77% 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..a3e06e2f6e9 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 @@ -1,24 +1,24 @@ /* * 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.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; @@ -31,7 +31,8 @@ public class BasicConfigurationFactory extends ConfigurationFactory { @Override - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + public Configuration getConfiguration( + final LoggerContext loggerContext, final String name, final URI configLocation) { return new BasicConfiguration(); } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/Compiler.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/Compiler.java new file mode 100644 index 00000000000..d2a573276dc --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/Compiler.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.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +public class Compiler { + public static void compile(final File source, final String... compilerOptions) throws IOException { + compile(Collections.singletonList(source), compilerOptions); + } + + public static void compile(final Iterable sources, final String... compilerOptions) + throws IOException { + 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(sources); + final List options = Arrays.asList(compilerOptions); + compiler.getTask(null, fileManager, diagnostics, options, 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(diagnostic.toString()); + } + } + } + assertThat(errors).isEmpty(); + } +} 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 76% 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..00463428130 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 @@ -1,25 +1,24 @@ /* * 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.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 { @@ -38,7 +37,8 @@ public static void stopLoggerContext(final boolean currentContext) { ((LifeCycle) LogManager.getContext(currentContext)).stop(); // stops async thread } - public static void stopLoggerContext(final boolean currentContext, final File checkFilePresence) throws InterruptedException { + public static void stopLoggerContext(final boolean currentContext, final File checkFilePresence) + throws InterruptedException { stopLoggerContext(currentContext); sleepAndCheck(checkFilePresence); } @@ -47,5 +47,4 @@ public static void stopLoggerContext(final File checkFilePresence) throws Interr stopLoggerContext(); sleepAndCheck(checkFilePresence); } - } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.java new file mode 100644 index 00000000000..678b484e3d5 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.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.test; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.plugins.Plugin; + +/** + * + */ +@Plugin(name = "ExtendedLevel", category = Level.CATEGORY) +public class ExtendedLevels { + + public static final Level NOTE = Level.forName("NOTE", 350); + public static final Level DETAIL = Level.forName("DETAIL", 450); +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GcFreeLoggingTestUtil.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GcFreeLoggingTestUtil.java new file mode 100644 index 00000000000..ace41854670 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GcFreeLoggingTestUtil.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.monitoring.runtime.instrumentation.AllocationRecorder; +import com.google.monitoring.runtime.instrumentation.Sampler; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.File; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import java.util.stream.Stream; +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.util.Constants; +import org.apache.logging.log4j.message.StringMapMessage; + +/** + * Utility methods for the GC-free logging tests. + */ +public enum GcFreeLoggingTestUtil { + ; + + public static void executeLogging(final String configurationFile, final Class testClass) throws Exception { + + System.setProperty("log4j2.enableThreadlocals", "true"); + System.setProperty("log4j2.enableDirectEncoders", "true"); + System.setProperty("log4j2.configurationFile", configurationFile); + System.setProperty("log4j2.clock", "SystemMillisClock"); + + assertTrue(Constants.ENABLE_THREADLOCALS, "Constants.ENABLE_THREADLOCALS"); + assertTrue(Constants.ENABLE_DIRECT_ENCODERS, "Constants.ENABLE_DIRECT_ENCODERS"); + + final MyCharSeq myCharSeq = new MyCharSeq(); + final Marker testGrandParent = MarkerManager.getMarker("testGrandParent"); + final Marker testParent = MarkerManager.getMarker("testParent").setParents(testGrandParent); + final Marker test = MarkerManager.getMarker("test").setParents(testParent); // initial creation, value is cached + final StringMapMessage mapMessage = new StringMapMessage().with("eventId", "Login"); + + // initialize LoggerContext etc. + // This is not steady-state logging and will allocate objects. + ThreadContext.put("aKey", "value1"); + ThreadContext.put("key2", "value2"); + + final org.apache.logging.log4j.Logger logger = LogManager.getLogger(testClass.getName()); + logger.debug("debug not set"); + logger.fatal(test, "This message is logged to the console"); + logger.error("Sample error message"); + logger.error("Test parameterized message {}", "param"); + logger.error(new StringMapMessage().with("eventId", "Login")); // initialize GelfLayout's messageStringBuilder + singleLoggingIteration(logger, myCharSeq, mapMessage); + for (int i = 0; i < 256; i++) { + logger.debug("ensure all ringbuffer slots have been used once"); // allocate MutableLogEvent.messageText + } + ThreadContext.remove("aKey"); + ThreadContext.remove("key2"); + + // BlockingWaitStrategy uses ReentrantLock which allocates Node objects. Ignore this. + final String[] exclude = new String[] { + "java/util/concurrent/locks/AbstractQueuedSynchronizer$Node", // + "com/google/monitoring/runtime/instrumentation/Sampler" + }; + final AtomicBoolean samplingEnabled = new AtomicBoolean(true); + final Sampler sampler = (count, desc, newObj, size) -> { + if (!samplingEnabled.get()) { + return; + } + for (int i = 0; i < exclude.length; i++) { + if (exclude[i].equals(desc)) { + return; // exclude + } + } + System.err.println("I just allocated the object " + newObj + " of type " + desc + " whose size is " + size); + if (count != -1) { + System.err.println("It's an array of size " + count); + } + + // show a stack trace to see which line caused allocation + new RuntimeException().printStackTrace(); + }; + Thread.sleep(500); + AllocationRecorder.addSampler(sampler); + + // now do some steady-state logging + + ThreadContext.put("aKey", "value1"); + ThreadContext.put("key2", "value2"); + + final int ITERATIONS = 5; + for (int i = 0; i < ITERATIONS; i++) { + singleLoggingIteration(logger, myCharSeq, mapMessage); + ThreadContext.remove("aKey"); + ThreadContext.put("aKey", "value1"); + } + Thread.sleep(50); + samplingEnabled.set(false); // reliably ignore all allocations from now on + AllocationRecorder.removeSampler(sampler); + Thread.sleep(100); + } + + private static void singleLoggingIteration( + final org.apache.logging.log4j.Logger logger, + final MyCharSeq myCharSeq, + final StringMapMessage mapMessage) { + logger.isEnabled(Level.TRACE); + logger.isEnabled(Level.TRACE, MarkerManager.getMarker("test")); + logger.isTraceEnabled(); + logger.isTraceEnabled(MarkerManager.getMarker("test")); + logger.trace(myCharSeq); + logger.trace(MarkerManager.getMarker("test"), myCharSeq); + logger.trace("Test message"); + logger.trace("Test parameterized message {}", "param"); + logger.trace("Test parameterized message {}{}", "param", "param2"); + logger.trace("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.trace(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.trace(mapMessage); // LOG4J2-1683 + + logger.isEnabled(Level.DEBUG); + logger.isEnabled(Level.DEBUG, MarkerManager.getMarker("test")); + logger.isDebugEnabled(); + logger.isDebugEnabled(MarkerManager.getMarker("test")); + logger.debug(myCharSeq); + logger.debug(MarkerManager.getMarker("test"), myCharSeq); + logger.debug("Test message"); + logger.debug("Test parameterized message {}", "param"); + logger.debug("Test parameterized message {}{}", "param", "param2"); + logger.debug("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.debug(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.debug(mapMessage); // LOG4J2-1683 + + logger.isEnabled(Level.INFO); + logger.isEnabled(Level.INFO, MarkerManager.getMarker("test")); + logger.isInfoEnabled(); + logger.isInfoEnabled(MarkerManager.getMarker("test")); + logger.info(myCharSeq); + logger.info(MarkerManager.getMarker("test"), myCharSeq); + logger.info("Test message"); + logger.info("Test parameterized message {}", "param"); + logger.info("Test parameterized message {}{}", "param", "param2"); + logger.info("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.info(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.info(mapMessage); // LOG4J2-1683 + + logger.isEnabled(Level.WARN); + logger.isEnabled(Level.WARN, MarkerManager.getMarker("test")); + logger.isWarnEnabled(); + logger.isWarnEnabled(MarkerManager.getMarker("test")); + logger.warn(myCharSeq); + logger.warn(MarkerManager.getMarker("test"), myCharSeq); + logger.warn("Test message"); + logger.warn("Test parameterized message {}", "param"); + logger.warn("Test parameterized message {}{}", "param", "param2"); + logger.warn("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.warn(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.warn(mapMessage); // LOG4J2-1683 + + logger.isEnabled(Level.ERROR); + logger.isEnabled(Level.ERROR, MarkerManager.getMarker("test")); + logger.isErrorEnabled(); + logger.isErrorEnabled(MarkerManager.getMarker("test")); + logger.error(myCharSeq); + logger.error(MarkerManager.getMarker("test"), myCharSeq); + logger.error("Test message"); + logger.error("Test parameterized message {}", "param"); + logger.error("Test parameterized message {}{}", "param", "param2"); + logger.error("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.error(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.error(mapMessage); // LOG4J2-1683 + + logger.isEnabled(Level.FATAL); + logger.isEnabled(Level.FATAL, MarkerManager.getMarker("test")); + logger.isFatalEnabled(); + logger.isFatalEnabled(MarkerManager.getMarker("test")); + logger.fatal(myCharSeq); + logger.fatal(MarkerManager.getMarker("test"), myCharSeq); + logger.fatal("Test message"); + logger.fatal("Test parameterized message {}", "param"); + logger.fatal("Test parameterized message {}{}", "param", "param2"); + logger.fatal("Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.fatal(MarkerManager.getMarker("test"), "Test parameterized message {}{}{}", "param", "param2", "abc"); + logger.fatal(mapMessage); // LOG4J2-1683 + } + + @SuppressFBWarnings("COMMAND_INJECTION") + public static void runTest(final Class cls) throws Exception { + final String javaHome = System.getProperty("java.home"); + final String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + final String classpath = System.getProperty("java.class.path"); + final String javaagent = "-javaagent:" + agentJar(); + + final File tempFile = File.createTempFile("allocations", ".txt"); + tempFile.deleteOnExit(); + + final ProcessBuilder builder = new ProcessBuilder( // + javaBin, javaagent, "-cp", classpath, cls.getName()); + builder.redirectError(ProcessBuilder.Redirect.to(tempFile)); + builder.redirectOutput(ProcessBuilder.Redirect.to(tempFile)); + final Process process = builder.start(); + process.waitFor(); + process.exitValue(); + + final AtomicInteger lineCounter = new AtomicInteger(0); + try (final Stream lines = Files.lines(tempFile.toPath(), Charset.defaultCharset())) { + final Pattern pattern = + Pattern.compile(String.format("^FATAL .*\\.%s [main].*", Pattern.quote(cls.getSimpleName()))); + assertThat(lines.flatMap(l -> { + final int lineNumber = lineCounter.incrementAndGet(); + final String line = l.trim(); + return pattern.matcher(line).matches() ? Stream.of(lineNumber + ": " + line) : Stream.empty(); + })) + .isEmpty(); + } + } + + private static File agentJar() { + final String name = AllocationRecorder.class.getName(); + final URL url = AllocationRecorder.class.getResource( + "/" + name.replace('.', '/').concat(".class")); + if (url == null) { + throw new IllegalStateException("Could not find url for " + name); + } + final String temp = url.toString(); + final String path = temp.substring("jar:file:".length(), temp.indexOf('!')); + return new File(path); + } + + public static class MyCharSeq implements CharSequence { + final String seq = GcFreeLoggingTestUtil.class.toString(); + + @Override + public int length() { + return seq.length(); + } + + @Override + public char charAt(final int index) { + return seq.charAt(index); + } + + @Override + public CharSequence subSequence(final int start, final int end) { + return seq.subSequence(start, end); + } + + @Override + public String toString() { + System.err.println("TEMP OBJECT CREATED!"); + throw new IllegalStateException("TEMP OBJECT CREATED!"); + } + } +} 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 78% 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..d6fd348ebc2 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 @@ -1,21 +1,20 @@ /* * 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.logging.log4j.test; +package org.apache.logging.log4j.core.test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; @@ -27,7 +26,7 @@ public class RuleChainFactory { /** * Creates a {@link RuleChain} where the rules are evaluated in the order you pass in. - * + * * @param testRules * test rules to evaluate * @return a new rule chain. 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..18f68a94a7b --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/SystemPropertyTestRule.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; + +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-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.java new file mode 100644 index 00000000000..c36ba25fb49 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.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.core.test; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; + +/** + * Markers useful in tests. + */ +public class TestMarkers { + + public static final Marker LIFE_CYCLE = MarkerManager.getMarker("LIFECYCLE"); + public static final Marker TEST = MarkerManager.getMarker("TEST"); + public static final Marker TEST_RULE = MarkerManager.getMarker("TEST_RULE").addParents(TEST); + public static final Marker TEST_RULE_LIFE_CYCLE = MarkerManager.getMarker("TEST_RULE_LIFE_CYCLE") + .addParents(TEST_RULE) + .addParents(LIFE_CYCLE); +} 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..0bdccf49132 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/AlwaysFailAppender.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.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.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.validation.constraints.Required; + +/** + * + */ +@Plugin(name = "AlwaysFail", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true) +public class AlwaysFailAppender extends AbstractAppender { + + private AlwaysFailAppender(final String name) { + super(name, null, null, false, Property.EMPTY_ARRAY); + } + + @Override + public void append(final LogEvent event) { + throw new LoggingException("Always fail"); + } + + @PluginFactory + public static AlwaysFailAppender createAppender( + @PluginAttribute("name") @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..8f352ab63ef --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/BlockingAppender.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.test.appender; + +import java.util.concurrent.TimeUnit; +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.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.validation.constraints.Required; + +/** + * + */ +@Plugin(name = "Block", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true) +public class BlockingAppender extends AbstractAppender { + public volatile boolean running = true; + + private BlockingAppender(final String name) { + super(name, null, null, false, Property.EMPTY_ARRAY); + } + + @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("name") @Required(message = "A name for the Appender must be specified") + final String name) { + return new BlockingAppender(name); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java index 4d7ee5632ed..764d36022f7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java @@ -1,30 +1,29 @@ /* * 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.logging.log4j.test.appender; +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; import org.apache.logging.log4j.core.layout.SerializedLayout; -import java.io.Serializable; -import java.nio.ByteBuffer; - /** * This appender is primarily used for testing. Use in a real environment is discouraged as the * List could eventually grow to cause an OutOfMemoryError. @@ -37,12 +36,19 @@ public EncodingListAppender(final String name) { super(name); } - public EncodingListAppender(final String name, final Filter filter, final Layout layout, final boolean newline, final boolean raw) { + public EncodingListAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean newline, + final boolean raw) { super(name, filter, layout, newline, raw); } - private class Destination implements ByteBufferDestination { - ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[4096]); + private static class Destination implements ByteBufferDestination { + // JUnit 5 stack traces can start to get looooong + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16384]); + @Override public ByteBuffer getByteBuffer() { return byteBuffer; @@ -50,7 +56,7 @@ public ByteBuffer getByteBuffer() { @Override public ByteBuffer drain(final ByteBuffer buf) { - throw new IllegalStateException("Unexpected message larger than 4096 bytes"); + throw new IllegalStateException("Unexpected message larger than 16384 bytes"); } @Override @@ -86,5 +92,4 @@ public synchronized void append(final LogEvent event) { 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..242357ddd7e --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/FailOnceAppender.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +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.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.validation.constraints.Required; +import org.apache.logging.log4j.core.util.Throwables; + +/** + * An {@link Appender} that fails on the first use and works for the rest. + */ +@Plugin(name = "FailOnce", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true) +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; + final 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-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.java new file mode 100644 index 00000000000..8b29ca235b5 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.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.appender; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +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; + +/** + * + */ +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, + Property.EMPTY_ARRAY, + new InMemoryManager(name, layout, writeHeader)); + } + + @Override + public String toString() { + return getManager().toString(); + } + + static class InMemoryManager extends OutputStreamManager { + + public InMemoryManager( + final String name, final Layout layout, final boolean writeHeader) { + super(new ByteArrayOutputStream(), name, layout, writeHeader); + } + + @Override + public String toString() { + try { + return getOutputStream().toString(); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } + } +} 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..f941db12674 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +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.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +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.layout.SerializedLayout; +import org.awaitility.Awaitility; + +/** + * 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 org.apache.logging.log4j.core.test.junit.LoggerContextRule#getListAppender(String) ILC.getListAppender + */ +@Plugin(name = "List", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +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 && !(layout instanceof SerializedLayout)) { + final byte[] bytes = layout.getHeader(); + if (bytes != null) { + write(bytes); + } + } + } + + @Override + public void append(final LogEvent event) { + final Layout layout = getLayout(); + if (layout == null) { + events.add(event.toImmutable()); + } else if (layout instanceof SerializedLayout) { + final byte[] header = layout.getHeader(); + final byte[] content = layout.toByteArray(event); + final byte[] record = new byte[header.length + content.length]; + System.arraycopy(header, 0, record, 0, header.length); + System.arraycopy(content, 0, record, header.length, content.length); + data.add(record); + } 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 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 { + Awaitility.waitAtMost(timeout, timeUnit).until(() -> messages.size() >= minSize); + return getMessages(); + } + + /** Returns an immutable snapshot of captured data */ + public List getData() { + return Collections.unmodifiableList(new ArrayList<>(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); + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + @Required + private String name; + + @PluginBuilderAttribute + private boolean entryPerNewLine; + + @PluginBuilderAttribute + private boolean raw; + + @PluginElement("Layout") + private Layout layout; + + @PluginElement("Filter") + private Filter filter; + + public Builder setName(final String name) { + this.name = name; + return this; + } + + public Builder setEntryPerNewLine(final boolean entryPerNewLine) { + this.entryPerNewLine = entryPerNewLine; + return this; + } + + public Builder setRaw(final boolean raw) { + this.raw = raw; + return this; + } + + public Builder setLayout(final Layout layout) { + this.layout = layout; + return this; + } + + public Builder setFilter(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 org.apache.logging.log4j.core.test.junit.LoggerContextRule#getListAppender(String) + */ + public static ListAppender getListAppender(final String name) { + return ((ListAppender) + (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-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/JdbcH2TestHelper.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/JdbcH2TestHelper.java new file mode 100644 index 00000000000..95ee5330ca0 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/JdbcH2TestHelper.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.test.appender.db.jdbc; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import org.apache.logging.log4j.core.appender.db.jdbc.AbstractConnectionSource; +import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource; + +@SuppressFBWarnings("HARD_CODE_PASSWORD") +public class JdbcH2TestHelper { + + /** + * A JDBC connection string for an H2 in-memory database. + */ + public static final String CONNECTION_STRING_IN_MEMORY = "jdbc:h2:mem:Log4j"; + + /** + * A JDBC connection string for a permanent H2 database. + * + * Since 2.22.0 this uses a permanent in-memory database. + */ + @Deprecated + public static final String CONNECTION_STRING_TEMP_DIR = "jdbc:h2:mem:Log4j_perm;DB_CLOSE_DELAY=-1"; + + public static final String USER_NAME = "sa"; + public static final String PASSWORD = ""; + + public static ConnectionSource TEST_CONFIGURATION_SOURCE_MEM = new AbstractConnectionSource() { + @Override + public Connection getConnection() throws SQLException { + return JdbcH2TestHelper.getConnectionInMemory(); + } + }; + + @Deprecated + public static ConnectionSource TEST_CONFIGURATION_SOURCE_TMPDIR = new AbstractConnectionSource() { + @Override + public Connection getConnection() throws SQLException { + return JdbcH2TestHelper.getConnectionTempDir(); + } + }; + + @Deprecated + public static void deleteDir() throws IOException { + // Since 2.22.0 this is a no-op + } + + @SuppressFBWarnings(value = "DMI_EMPTY_DB_PASSWORD") + public static Connection getConnectionInMemory() throws SQLException { + return DriverManager.getConnection(CONNECTION_STRING_IN_MEMORY, USER_NAME, PASSWORD); + } + + /** + * Since 2.22.0 this uses a permanent in-memory database. + */ + @Deprecated + @SuppressFBWarnings(value = "DMI_EMPTY_DB_PASSWORD") + public static Connection getConnectionTempDir() throws SQLException { + return DriverManager.getConnection(CONNECTION_STRING_TEMP_DIR, USER_NAME, PASSWORD); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/package-info.java new file mode 100644 index 00000000000..638390f63ab --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/db/jdbc/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. + */ +@Export +@Version("2.20.3") +package org.apache.logging.log4j.core.test.appender.db.jdbc; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/package-info.java new file mode 100644 index 00000000000..c7a9719884f --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.test.appender; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 78% 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..75ae3218172 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,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.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-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/package-info.java new file mode 100644 index 00000000000..6e187c26a4b --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.test.appender.rolling.action; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.java new file mode 100644 index 00000000000..665eb6a6918 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.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.test.categories; + +/** + * Categories for appenders that require extra dependencies. + */ +public interface Appenders { + interface AsyncConversant {} + + interface AsyncJcTools {} + + interface Cassandra {} + + interface CouchDb {} + + interface Jms {} + + interface Jpa {} + + interface Kafka {} + + interface MongoDb {} + + interface Smtp {} + + interface ZeroMq {} +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.java new file mode 100644 index 00000000000..4179509fabe --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.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.test.categories; + +/** + * Category for tests related to AsyncLogger (requires LMAX Disruptor dependency). + */ +public interface AsyncLoggers {} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.java new file mode 100644 index 00000000000..4855b0a9846 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.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.core.test.categories; + +/** + * Categories for configuration formats that require extra dependencies. + */ +public interface Configurations { + interface Json {} + + interface Yaml {} +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java new file mode 100644 index 00000000000..89057c90c08 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.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.test.categories; + +/** + * Categories for layouts that require extra dependencies. + */ +public interface Layouts { + interface Csv {} + + interface Json {} + + interface Xml {} + + interface Yaml {} +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.java new file mode 100644 index 00000000000..46cfe7a6d24 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.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.test.categories; + +/** + * JUnit category to indicate a test is primarily for testing performance. + */ +public interface PerformanceTests {} 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..5ce99213312 --- /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..fbbe4172853 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/package-info.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. + */ + +/** + * 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. + */ +@Export +@Version("2.20.2") +@BaselineIgnore("2.25.0") +package org.apache.logging.log4j.core.test.categories; + +import aQute.bnd.annotation.baseline.BaselineIgnore; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 78% 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..7628a51746d 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 @@ -1,20 +1,20 @@ /* * 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.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 89% 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..bfd1d1a1333 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 @@ -1,31 +1,30 @@ /* * 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.logging.log4j.hamcrest; - -import java.io.File; - -import org.hamcrest.FeatureMatcher; -import org.hamcrest.Matcher; +package org.apache.logging.log4j.core.test.hamcrest; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.number.OrderingComparison.greaterThan; import static org.hamcrest.number.OrderingComparison.lessThanOrEqualTo; +import java.io.File; +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + /** * Hamcrest Matchers that operate on File objects. * @@ -123,8 +122,8 @@ public static Matcher beforeNow() { * @return the Matcher. */ public static Matcher hasNumberOfFiles(final Matcher matcher) { - return new FeatureMatcher(matcher, "directory with number of files", - "directory with number of files") { + return new FeatureMatcher( + matcher, "directory with number of files", "directory with number of files") { @Override protected Integer featureValueOf(final File actual) { final File[] files = actual.listFiles(); @@ -157,7 +156,5 @@ protected String featureValueOf(final File actual) { }; } - private FileMatchers() { - } - + private FileMatchers() {} } 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 82% 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..d559530a7b6 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 @@ -1,28 +1,27 @@ /* * 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.logging.log4j.hamcrest; +package org.apache.logging.log4j.core.test.hamcrest; -import java.util.Map; +import static org.hamcrest.core.IsEqual.equalTo; +import java.util.Map; import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; -import static org.hamcrest.core.IsEqual.equalTo; - /** * Hamcrest Matchers for Maps. * @@ -57,6 +56,5 @@ protected Integer featureValueOf(final Map actual) { return hasSize(equalTo(size)); } - private MapMatchers() { - } + private MapMatchers() {} } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/package-info.java new file mode 100644 index 00000000000..63fa7da30f5 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.test.hamcrest; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AbstractExternalFileCleaner.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AbstractExternalFileCleaner.java new file mode 100644 index 00000000000..4477b55da72 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AbstractExternalFileCleaner.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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); + } + } + + @SuppressFBWarnings("INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE") + 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-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..03cd82eb713 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.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 static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getParameterLoggerContext; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +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; + +class AppenderResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + return Appender.class.isAssignableFrom(parameterContext.getParameter().getType()) + && parameterContext.isAnnotated(Named.class); + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final LoggerContext loggerContext = getParameterLoggerContext(parameterContext, extensionContext); + if (loggerContext == null) { + throw new ParameterResolutionException("No LoggerContext defined"); + } + final String name = parameterContext + .findAnnotation(Named.class) + .map(Named::value) + .map(s -> s.isEmpty() ? parameterContext.getParameter().getName() : s) + .orElseThrow(() -> new ParameterResolutionException("No @Named 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/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/CleanFiles.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/CleanFiles.java index de95bfb2ddd..1a263e12a96 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/CleanFiles.java @@ -1,20 +1,20 @@ /* * 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.logging.log4j.junit; +package org.apache.logging.log4j.core.test.junit; import java.io.File; import java.io.IOException; @@ -26,11 +26,15 @@ *

* 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 { @@ -60,5 +64,4 @@ public CleanFiles(final String... fileNames) { protected boolean clean(final Path path, final int tryIndex) throws IOException { return Files.deleteIfExists(path); } - } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/CleanFolders.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/CleanFolders.java new file mode 100644 index 00000000000..426c084a1fe --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/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.core.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("%s Deleted file %s: %s\n", CLEANER_MARKER, 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-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..efb66056466 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.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.test.junit; + +import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getParameterLoggerContext; + +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; + +class ConfigurationResolver extends TypeBasedParameterResolver { + + public ConfigurationResolver() { + super(Configuration.class); + } + + @Override + public Configuration resolveParameter( + final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final LoggerContext loggerContext = getParameterLoggerContext(parameterContext, 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/JdbcRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/JdbcRule.java new file mode 100644 index 00000000000..d6f76b4fa26 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/JdbcRule.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.test.junit; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Objects; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource; +import org.junit.rules.TestRule; +import org.junit.runner.Description; + +/** + * JUnit rule to set up a database. This will create a table using the configure creation statement on startup, run its + * wrapped test(s), then execute a drop table statement on shutdown, and finally will attempt execute a {@code SHUTDOWN} + * command afterward (e.g., for H2, HSQLDB). When used in integration tests, this rule should be the outer rule in a + * chain with {@link LoggerContextRule}. + * + * @since 2.8 + */ +@SuppressFBWarnings("SQL_INJECTION_JDBC") +public class JdbcRule implements TestRule { + + private final ConnectionSource connectionSource; + private final String createTableStatement; + private final String dropTableStatement; + + /** + * Creates a JdbcRule using a {@link ConnectionSource} and a table creation statement. + * + * @param connectionSource a required source for obtaining a Connection. + * @param createTableStatement an optional SQL DDL statement to create a table for use in a JUnit test. + * @param dropTableStatement an optional SQL DDL statement to drop the created table. + */ + public JdbcRule( + final ConnectionSource connectionSource, + final String createTableStatement, + final String dropTableStatement) { + this.connectionSource = Objects.requireNonNull(connectionSource, "connectionSource"); + this.createTableStatement = createTableStatement; + this.dropTableStatement = dropTableStatement; + } + + @Override + public org.junit.runners.model.Statement apply( + final org.junit.runners.model.Statement base, final Description description) { + return new org.junit.runners.model.Statement() { + @Override + public void evaluate() throws Throwable { + try (final Connection connection = getConnection(); + final Statement statement = connection.createStatement()) { + try { + if (StringUtils.isNotEmpty(createTableStatement)) { + statement.executeUpdate(createTableStatement); + } + base.evaluate(); + } finally { + if (StringUtils.isNotEmpty(dropTableStatement)) { + statement.executeUpdate(dropTableStatement); + } + statement.execute("SHUTDOWN"); + } + } + } + }; + } + + public Connection getConnection() throws SQLException { + return connectionSource.getConnection(); + } + + public ConnectionSource getConnectionSource() { + return connectionSource; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/JndiRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/JndiRule.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/JndiRule.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/JndiRule.java index 7638d2c0033..17fcb9cdd9e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/JndiRule.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/JndiRule.java @@ -1,25 +1,24 @@ /* * 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.logging.log4j.junit; +package org.apache.logging.log4j.core.test.junit; import java.util.Collections; import java.util.Map; import javax.naming.Context; - import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -55,5 +54,4 @@ public void evaluate() throws Throwable { } }; } - } 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..62ff8154ce4 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.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.core.test.junit; + +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.LoggerContextAccessor; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; +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.ExtensionContextException; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.platform.commons.support.AnnotationSupport; + +class LoggerContextResolver extends TypeBasedParameterResolver + implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback { + + 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 -> { + final LoggerContextConfig config = new LoggerContextConfig(testSource, context); + getTestClassStore(context).put(LoggerContext.class, config); + }); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + final LoggerContextConfig config = + getTestClassStore(context).get(LoggerContext.class, LoggerContextConfig.class); + if (config != null) { + config.close(); + } + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { + final LoggerContextConfig config = + getTestClassStore(context).get(LoggerContext.class, LoggerContextConfig.class); + if (config == null) { + throw new IllegalStateException( + "Specified @LoggerContextSource but no LoggerContext found for test class " + + testClass.getCanonicalName()); + } + if (config.reconfigurationPolicy == ReconfigurationPolicy.BEFORE_EACH) { + config.reconfigure(); + } + } + AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), LoggerContextSource.class) + .ifPresent(source -> { + final LoggerContextConfig config = new LoggerContextConfig(source, context); + if (config.reconfigurationPolicy == ReconfigurationPolicy.BEFORE_EACH) { + config.reconfigure(); + } + getTestInstanceStore(context).put(LoggerContext.class, config); + }); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + // method-annotated variant + final LoggerContextConfig testInstanceConfig = + getTestInstanceStore(context).get(LoggerContext.class, LoggerContextConfig.class); + if (testInstanceConfig != null) { + testInstanceConfig.close(); + } + // reloadable variant + final Class testClass = context.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { + final LoggerContextConfig config = + getTestClassStore(context).get(LoggerContext.class, LoggerContextConfig.class); + if (config == null) { + throw new IllegalStateException( + "Specified @LoggerContextSource but no LoggerContext found for test class " + + testClass.getCanonicalName()); + } + if (config.reconfigurationPolicy == ReconfigurationPolicy.AFTER_EACH) { + config.reconfigure(); + } + } + } + + @Override + public LoggerContext resolveParameter( + final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + return getParameterLoggerContext(parameterContext, extensionContext); + } + + private static ExtensionContext.Store getTestClassStore(final ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(LoggerContext.class, context.getRequiredTestClass())); + } + + private static ExtensionContext.Store getTestInstanceStore(final ExtensionContext context) { + return context.getStore( + ExtensionContext.Namespace.create(LoggerContext.class, context.getRequiredTestInstance())); + } + + static LoggerContext getParameterLoggerContext( + final ParameterContext parameterContext, final ExtensionContext extensionContext) { + if (parameterContext.getDeclaringExecutable() instanceof Method) { + final LoggerContextAccessor accessor = + getTestInstanceStore(extensionContext).get(LoggerContext.class, LoggerContextAccessor.class); + return accessor != null + ? accessor.getLoggerContext() + : getTestClassStore(extensionContext) + .get(LoggerContext.class, LoggerContextAccessor.class) + .getLoggerContext(); + } + return getTestClassStore(extensionContext) + .get(LoggerContext.class, LoggerContextAccessor.class) + .getLoggerContext(); + } + + private static final class LoggerContextConfig implements AutoCloseable, LoggerContextAccessor { + private final LoggerContext context; + private final ReconfigurationPolicy reconfigurationPolicy; + private final long shutdownTimeout; + private final TimeUnit unit; + + private LoggerContextConfig(final LoggerContextSource source, final ExtensionContext extensionContext) { + final String displayName = extensionContext.getDisplayName(); + final ClassLoader classLoader = + extensionContext.getRequiredTestClass().getClassLoader(); + context = Configurator.initialize(displayName, classLoader, getConfigLocation(source, extensionContext)); + reconfigurationPolicy = source.reconfigure(); + shutdownTimeout = source.timeout(); + unit = source.unit(); + } + + private static String getConfigLocation( + final LoggerContextSource source, final ExtensionContext extensionContext) { + final String value = source.value(); + if (value.isEmpty()) { + Class clazz = extensionContext.getRequiredTestClass(); + while (clazz != null) { + final URL url = clazz.getResource(clazz.getSimpleName() + ".xml"); + if (url != null) { + try { + return url.toURI().toString(); + } catch (URISyntaxException e) { + throw new ExtensionContextException("An error occurred accessing the configuration.", e); + } + } + clazz = clazz.getSuperclass(); + } + return extensionContext.getRequiredTestClass().getName().replaceAll("[.$]", "/") + ".xml"; + } + return value; + } + + @Override + public LoggerContext getLoggerContext() { + return context; + } + + public void reconfigure() { + context.reconfigure(); + } + + @Override + public void close() { + context.stop(shutdownTimeout, unit); + } + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java new file mode 100644 index 00000000000..b4edc8448d5 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.Assert.assertNotNull; + +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.AbstractLifeCycle; +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.LoggerContextAccessor; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * 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 configurationLocation; + private LoggerContext loggerContext; + private Class contextSelectorClass; + private String testClassName; + private final long shutdownTimeout; + private final TimeUnit shutdownTimeUnit; + + /** + * Constructs a new LoggerContextRule without a configuration file. + */ + public LoggerContextRule() { + this(null, null); + } + + /** + * Constructs a new LoggerContextRule for a given configuration file. + * + * @param configurationLocation + * path to configuration file + */ + public LoggerContextRule(final String configurationLocation) { + this(configurationLocation, null); + } + + /** + * Constructs a new LoggerContextRule for a given configuration file and a custom {@link ContextSelector} class. + * + * @param configurationLocation + * path to configuration file + * @param contextSelectorClass + * custom ContextSelector class to use instead of default + */ + public LoggerContextRule( + final String configurationLocation, final Class contextSelectorClass) { + this( + configurationLocation, + contextSelectorClass, + AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, + AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); + } + + public LoggerContextRule( + final String configurationLocation, + final Class contextSelectorClass, + final long shutdownTimeout, + final TimeUnit shutdownTimeUnit) { + this.configurationLocation = configurationLocation; + this.contextSelectorClass = contextSelectorClass; + this.shutdownTimeout = shutdownTimeout; + this.shutdownTimeUnit = shutdownTimeUnit; + } + + public LoggerContextRule( + final String configurationLocation, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) { + this(configurationLocation, null, shutdownTimeout, shutdownTimeUnit); + } + + @Override + public Statement apply(final Statement base, final Description description) { + // Hack: Using -DEBUG as a JVM param sets a property called "EBUG"... + if (System.getProperties().containsKey("EBUG")) { + StatusLogger.getLogger().setLevel(Level.DEBUG); + } + testClassName = description.getClassName(); + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (contextSelectorClass != null) { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, contextSelectorClass.getName()); + } + // 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(), + configurationLocation); + try { + base.evaluate(); + } finally { + if (!Configurator.shutdown(loggerContext, shutdownTimeout, shutdownTimeUnit)) { + StatusLogger.getLogger() + .error( + "Logger context {} did not shutdown completely after {} {}.", + loggerContext.getName(), + shutdownTimeout, + shutdownTimeUnit); + } + 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); + } + } + }; + } + + /** + * Gets a named Appender for this LoggerContext. + * + * @param name + * the name of the Appender to look up. + * @return the named Appender or {@code null} if it wasn't defined in the configuration. + */ + @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. + * + * @param + * The target Appender class + * @param name + * the name of the Appender to look up. + * @param cls + * The target Appender class + * @return the named Appender or {@code null} if it wasn't defined in the configuration. + */ + public T getAppender(final String name, final Class cls) { + return cls.cast(getConfiguration().getAppenders().get(name)); + } + + /** + * Gets the associated Configuration for the configuration file this was constructed with. + * + * @return this LoggerContext's Configuration. + */ + 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. + * + * @return the current LoggerContext. + */ + @Override + public LoggerContext getLoggerContext() { + return loggerContext; + } + + /** + * Gets a named ListAppender or throws an exception for this LoggerContext. + * + * @param name + * the name of the ListAppender to look up. + * @return the named ListAppender. + * @throws AssertionError + * if the named ListAppender doesn't exist or isn't a ListAppender. + */ + public ListAppender getListAppender(final String name) { + final Appender appender = getAppender(name); + if (appender instanceof ListAppender) { + return (ListAppender) appender; + } + throw new AssertionError("No ListAppender named " + name + " found."); + } + + /** + * Gets a named Logger using the test class's name from this LoggerContext. + * + * @return the test class's named Logger. + */ + public Logger getLogger() { + return loggerContext.getLogger(testClassName); + } + + /** + * Gets a named Logger for the given class in this LoggerContext. + * + * @param clazz + * The Class whose name should be used as the Logger name. If null it will default to the calling class. + * @return the named Logger. + */ + public Logger getLogger(final Class clazz) { + return loggerContext.getLogger(clazz.getName()); + } + + /** + * Gets a named Logger in this LoggerContext. + * + * @param name + * the name of the Logger to look up or create. + * @return the named Logger. + */ + public Logger getLogger(final String name) { + return loggerContext.getLogger(name); + } + + /** + * Gets a named Appender or throws an exception for this LoggerContext. + * + * @param name + * the name of the Appender to look up. + * @return the named Appender. + * @throws AssertionError + * if the Appender doesn't exist. + */ + public Appender getRequiredAppender(final String name) { + final Appender appender = getAppender(name); + assertNotNull("Appender named " + name + " was null.", appender); + return appender; + } + + /** + * Gets a named Appender or throws an exception for this LoggerContext. + * + * @param + * The target Appender class + * @param name + * the name of the Appender to look up. + * @param cls + * The target Appender class + * @return the named Appender. + * @throws AssertionError + * if the Appender doesn't exist. + */ + public T getRequiredAppender(final String name, final Class cls) { + final T appender = getAppender(name, cls); + assertNotNull("Appender named " + name + " was null in logger context " + loggerContext, appender); + return appender; + } + + /** + * Gets the root logger. + * + * @return the root logger. + */ + public Logger getRootLogger() { + return loggerContext.getRootLogger(); + } + + public void reconfigure() { + loggerContext.reconfigure(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("LoggerContextRule [configLocation="); + builder.append(configurationLocation); + builder.append(", contextSelectorClass="); + builder.append(contextSelectorClass); + builder.append("]"); + return builder.toString(); + } + + public RuleChain withCleanFilesRule(final String... files) { + return RuleChain.outerRule(new CleanFiles(files)).around(this); + } + + public RuleChain withCleanFoldersRule( + final boolean before, final boolean after, final int maxTries, final String... folders) { + return RuleChain.outerRule(new CleanFolders(before, after, maxTries, folders)) + .around(this); + } + + public RuleChain withCleanFoldersRule(final String... folders) { + return RuleChain.outerRule(new CleanFolders(folders)).around(this); + } +} 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..152766c95ce --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.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.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 java.util.concurrent.TimeUnit; +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.test.junit.TempLoggingDirectory; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * 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};
  • + *
  • any subclass of {@link Appender} 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({ + TempLoggingDirectory.class, + LoggerContextResolver.class, + ConfigurationResolver.class, + AppenderResolver.class +}) +public @interface LoggerContextSource { + /** + * Specifies the name of the configuration file to use for the annotated test. + *

+ * Defaults to the fully qualified name of the test class with '.xml' appended. + * E.g. this class would have a default of + * {@code org/apache/logging/log4j/core/test/junit/LoggerContextSource.xml}. + *

+ */ + String value() default ""; + + /** + * 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; +} 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..28857b89470 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Named.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.junit; + +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; + +/** + * 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 +public @interface Named { + /** + * Specifies the name of the configuration item to inject. If blank, uses the name of the annotated parameter. + */ + String value() default ""; +} 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..fea4fcd0c51 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ReconfigurationPolicy.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.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/Tags.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Tags.java new file mode 100644 index 00000000000..ddd0695f9b3 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Tags.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.test.junit; + +/** + * Container for a Junit 5 tags used in tests. + */ +public final class Tags { + + /** + * Tests that use LMAX Disruptor. Same name as the JUnit 4 category. + */ + public static final String ASYNC_LOGGERS = "org.apache.logging.log4j.core.test.categories.AsyncLoggers"; + + private Tags() {} +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestPropertyLookup.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestPropertyLookup.java new file mode 100644 index 00000000000..17bf81e0d67 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestPropertyLookup.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.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.lookup.AbstractLookup; +import org.apache.logging.log4j.core.lookup.StrLookup; +import org.apache.logging.log4j.test.junit.TestPropertySource; + +/** + * Resolves properties using {@link TestPropertySource}. + */ +@Plugin(name = "test", category = StrLookup.CATEGORY) +public class TestPropertyLookup extends AbstractLookup { + + @Override + public String lookup(LogEvent event, String key) { + return TestPropertySource.getProperties().getProperty(key); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/URLStreamHandlerFactoryRule.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/URLStreamHandlerFactoryRule.java index 2d68b583bab..b469508606a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/URLStreamHandlerFactoryRule.java @@ -1,28 +1,26 @@ /* * 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.logging.log4j.junit; +package org.apache.logging.log4j.core.test.junit; import java.lang.reflect.Field; import java.net.URL; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.Hashtable; - import org.junit.Assert; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -33,7 +31,11 @@ *

* Might need tweaking for different JREs. *

+ *

+ * Will be remove in version 3.x. + *

*/ +@Deprecated public class URLStreamHandlerFactoryRule implements TestRule { public URLStreamHandlerFactoryRule() { @@ -53,9 +55,8 @@ void clearURLHandlers() throws Exception { handlersFields.setAccessible(true); } @SuppressWarnings("unchecked") - final - Hashtable handlers = (Hashtable) handlersFields - .get(null); + final Hashtable handlers = + (Hashtable) handlersFields.get(null); if (handlers != null) { handlers.clear(); } @@ -79,9 +80,11 @@ public void evaluate() throws Throwable { break; } } - Assert.assertNotNull("java.net URL does not declare a java.net.URLStreamHandlerFactory field", - factoryField); - Assert.assertEquals("java.net.URL declares multiple java.net.URLStreamHandlerFactory fields.", 1, + Assert.assertNotNull( + "java.net URL does not declare a java.net.URLStreamHandlerFactory field", factoryField); + Assert.assertEquals( + "java.net.URL declares multiple java.net.URLStreamHandlerFactory fields.", + 1, matches); // FIXME There is a break in the loop so always 0 or 1 URL.setURLStreamHandlerFactory(newURLStreamHandlerFactory); try { 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..4103b148043 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.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. + */ + +/** + * JUnit helper classes and TestRules. + * @see org.junit.rules.TestRule + */ +@Export +@Version("2.23.1") +package org.apache.logging.log4j.core.test.junit; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.java new file mode 100644 index 00000000000..79f8bbd17f9 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.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.test.layout; + +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 org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.junit.CleanFolders; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1482 + */ +@Category(Layouts.Csv.class) +public abstract class Log4j2_1482_Test { + + static final String CONFIG_LOCATION = "log4j2-1482.xml"; + + static final String FOLDER = "target/log4j2-1482"; + + private static final int LOOP_COUNT = 10; + + static void assertFileContents(final int runNumber) throws IOException { + final Path path = Paths.get(FOLDER + "/audit.tmp"); + final List lines = Files.readAllLines(path, Charset.defaultCharset()); + int i = 1; + final int size = lines.size(); + for (final String string : lines) { + if (string.startsWith(",,")) { + final Path folder = Paths.get(FOLDER); + final File[] files = folder.toFile().listFiles(); + Arrays.sort(files); + System.out.println("Run " + runNumber + ": " + Arrays.toString(files)); + Assert.fail( + String.format("Run %,d, line %,d of %,d: \"%s\" in %s", runNumber, i++, size, string, lines)); + } + } + } + + @Rule + public CleanFolders cleanFolders = new CleanFolders(FOLDER); + + protected abstract void log(int runNumber); + + private void loopingRun(final int loopCount) throws IOException { + for (int i = 1; i <= loopCount; i++) { + try (final LoggerContext loggerContext = + Configurator.initialize(getClass().getName(), CONFIG_LOCATION)) { + log(i); + } + assertFileContents(i); + } + } + + @Test + public void testLoopingRun() throws IOException { + loopingRun(LOOP_COUNT); + } + + @Test + public void testSingleRun() throws IOException { + loopingRun(1); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/package-info.java new file mode 100644 index 00000000000..d0cb234d163 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.test.layout; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.java new file mode 100644 index 00000000000..ed41e412dd9 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.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.net.mock; + +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +public abstract class MockSyslogServer extends Thread { + + private static volatile int threadInitNumber; + protected static Logger LOGGER = StatusLogger.getLogger(); + + protected List messageList = new ArrayList<>(); + + @Deprecated + protected int port; + + @Deprecated + public MockSyslogServer(final int numberOfMessagesToReceive, final int port) { + this(); + this.port = port; + } + + public MockSyslogServer() { + setName(getClass().getSimpleName() + "-" + (++threadInitNumber)); + } + + public abstract int getLocalPort(); + + public void shutdown() {} + + public int getNumberOfReceivedMessages() { + return messageList.size(); + } + + public List getMessageList() { + return messageList; + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.java new file mode 100644 index 00000000000..ccd84a63b6f --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.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.test.net.mock; + +import java.io.IOException; +import java.net.SocketException; +import javax.net.ssl.SSLServerSocket; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; + +public class MockSyslogServerFactory { + + public static MockSyslogServer createUDPSyslogServer(final int numberOfMessagesToReceive, final int port) + throws SocketException { + return new MockUdpSyslogServer(numberOfMessagesToReceive, port); + } + + public static MockSyslogServer createUDPSyslogServer() throws SocketException { + return new MockUdpSyslogServer(); + } + + public static MockSyslogServer createTCPSyslogServer(final int numberOfMessagesToReceive, final int port) + throws IOException { + return new MockTcpSyslogServer(numberOfMessagesToReceive, port); + } + + public static MockSyslogServer createTCPSyslogServer() throws IOException { + return new MockTcpSyslogServer(); + } + + public static MockSyslogServer createTLSSyslogServer( + final int numberOfMessagesToReceive, + final TlsSyslogMessageFormat format, + final SSLServerSocket serverSocket) { + return new MockTlsSyslogServer(numberOfMessagesToReceive, format, serverSocket); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.java new file mode 100644 index 00000000000..7fecbe9ef81 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.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.test.net.mock; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; + +public class MockTcpSyslogServer extends MockSyslogServer { + private final ServerSocket serverSocket; + private volatile boolean shutdown = false; + private Thread thread; + + public MockTcpSyslogServer(final int numberOfMessagesToReceive, final int port) throws IOException { + this(port); + } + + public MockTcpSyslogServer() throws IOException { + this(0); + } + + @SuppressFBWarnings("UNENCRYPTED_SERVER_SOCKET") + private MockTcpSyslogServer(final int port) throws IOException { + super(0, port); + serverSocket = new ServerSocket(port); + } + + @Override + public int getLocalPort() { + return serverSocket.getLocalPort(); + } + + @Override + public void shutdown() { + this.shutdown = true; + try { + if (serverSocket != null) { + try { + this.serverSocket.close(); + } catch (final Exception e) { + LOGGER.error("The {} failed to close its socket.", getName(), e); + } + } + this.interrupt(); + } catch (final SecurityException e) { + LOGGER.error("Shutdown of {} failed", getName(), e); + } + if (thread != null) { + try { + thread.join(100); + } catch (final InterruptedException e) { + LOGGER.error("Shutdown of {} thread failed.", getName(), e); + } + } + } + + @Override + public void run() { + LOGGER.info("{} started on port {}.", getName(), getLocalPort()); + this.thread = Thread.currentThread(); + while (!shutdown) { + try { + final byte[] buffer = new byte[4096]; + try (final Socket socket = serverSocket.accept()) { + socket.setSoLinger(true, 0); + final InputStream in = socket.getInputStream(); + int i = in.read(buffer, 0, buffer.length); + while (i != -1) { + if (i < buffer.length) { + final String line = new String(buffer, 0, i); + messageList.add(line); + i = in.read(buffer, 0, buffer.length); + } else if (i == 0) { + LOGGER.warn("{} received no data.", getName()); + } else { + LOGGER.warn("{} received a message longer than {}.", getName(), buffer.length); + } + } + } + } catch (final Exception e) { + if (!shutdown) { + LOGGER.error("{} caught an exception.", getName(), e); + } + } + } + LOGGER.info("{} stopped.", getName()); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.java new file mode 100644 index 00000000000..0318d1b908b --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.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.core.test.net.mock; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; +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; + +public class MockTlsSyslogServer extends MockSyslogServer { + private final SSLServerSocket serverSocket; + private SSLSocket clientSocket; + private final List messageList = new ArrayList<>(); + private TlsSyslogInputStreamReaderBase syslogReader; + + private volatile boolean shutdown = false; + private Thread thread; + + private TlsSyslogMessageFormat messageFormat = TlsSyslogMessageFormat.SYSLOG; + private final int numberOfMessageToReceive; + + public MockTlsSyslogServer( + final int numberOfMessagesToReceive, + final TlsSyslogMessageFormat format, + final SSLServerSocket serverSocket) { + this.messageFormat = format; + this.numberOfMessageToReceive = numberOfMessagesToReceive; + this.serverSocket = serverSocket; + } + + @Override + public int getLocalPort() { + return serverSocket.getLocalPort(); + } + + @Override + public void shutdown() { + this.shutdown = true; + try { + if (serverSocket != null) { + try { + this.serverSocket.close(); + } catch (final Exception e) { + LOGGER.error("The {} failed to close its socket.", getName(), e); + } + } + this.interrupt(); + } catch (final SecurityException e) { + LOGGER.error("Shutdown of {} failed", getName(), e); + } + if (thread != null) { + try { + thread.join(100); + } catch (final InterruptedException e) { + LOGGER.error("Shutdown of {} thread failed.", getName(), e); + } + } + } + + @Override + public void run() { + LOGGER.info("{} started on port {}.", getName(), getLocalPort()); + this.thread = Thread.currentThread(); + try { + waitForConnection(); + processFrames(); + } catch (final Exception se) { + se.printStackTrace(); + } finally { + closeSockets(); + } + LOGGER.info("{} stopped.", getName()); + } + + private void waitForConnection() throws IOException { + clientSocket = (SSLSocket) serverSocket.accept(); + final InputStream clientSocketInputStream = clientSocket.getInputStream(); + syslogReader = createTLSSyslogReader(clientSocketInputStream); + } + + private TlsSyslogInputStreamReaderBase createTLSSyslogReader(final InputStream inputStream) { + switch (messageFormat) { + case SYSLOG: + return new TlsSyslogInputStreamReader(inputStream); + case LEGACY_BSD: + return new LegacyBsdTlsSyslogInputStreamReader(inputStream); + default: + return null; + } + } + + private void closeSockets() { + if (clientSocket != null) { + try { + clientSocket.close(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + } + + private synchronized void processFrames() throws IOException { + try { + int count = 0; + while (!shutdown) { + String message; + message = syslogReader.read(); + LOGGER.debug("{} received a message: {}", getName(), message); + messageList.add(message); + count++; + if (isEndOfMessages(count)) { + break; + } + } + this.notify(); + } catch (final Exception e) { + this.notify(); + throw new IOException(e); + } + } + + private boolean isEndOfMessages(final int count) { + return count == numberOfMessageToReceive; + } + + @Override + public List getMessageList() { + return messageList; + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.java new file mode 100644 index 00000000000..e1ff83e185e --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.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.test.net.mock; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import org.apache.logging.log4j.core.util.Throwables; + +public class MockUdpSyslogServer extends MockSyslogServer { + private final DatagramSocket socket; + private volatile boolean shutdown = false; + private Thread thread; + + public MockUdpSyslogServer(final int numberOfMessagesToReceive, final int port) throws SocketException { + this(port); + } + + public MockUdpSyslogServer() throws SocketException { + this(0); + } + + private MockUdpSyslogServer(final int port) throws SocketException { + super(0, port); + this.socket = new DatagramSocket(port); + } + + @Override + public int getLocalPort() { + return socket.getLocalPort(); + } + + @Override + public void shutdown() { + this.shutdown = true; + if (socket != null) { + socket.close(); + } + if (thread != null) { + thread.interrupt(); + try { + thread.join(100); + } catch (final InterruptedException e) { + LOGGER.error("Shutdown of {} thread failed.", getName(), e); + } + } + } + + @Override + public void run() { + LOGGER.info("{} started on port {}.", getName(), getLocalPort()); + this.thread = Thread.currentThread(); + final byte[] bytes = new byte[4096]; + final DatagramPacket packet = new DatagramPacket(bytes, bytes.length); + try { + while (!shutdown) { + socket.receive(packet); + final String message = new String(packet.getData(), 0, packet.getLength()); + LOGGER.debug("{} received a message: {}", getName(), message); + messageList.add(message); + } + } catch (final Exception e) { + if (!shutdown) { + Throwables.rethrow(e); + } + } + LOGGER.info("{} stopped.", getName()); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/package-info.java new file mode 100644 index 00000000000..466921018df --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/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. + */ +@Export +@Version("2.21.0") +package org.apache.logging.log4j.core.test.net.mock; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 82% 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..d75a563c8a3 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 @@ -1,26 +1,25 @@ /* * 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.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; - import org.apache.logging.log4j.util.Strings; public class LegacyBsdTlsSyslogInputStreamReader extends TlsSyslogInputStreamReaderBase { @@ -45,8 +44,7 @@ public String read() throws IOException { break; } } - } - catch (final EOFException e) { + } catch (final EOFException e) { if (buffer.size() > 0) { message = buffer.toString(); buffer.reset(); 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 82% 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..73865c6cb85 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 @@ -1,26 +1,27 @@ /* * 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.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import org.apache.logging.log4j.core.util.Integers; public class TlsSyslogInputStreamReader extends TlsSyslogInputStreamReaderBase { private static final char SPACE = ' '; @@ -44,7 +45,7 @@ public TlsSyslogInputStreamReader(final InputStream inputStream) { public String read() throws IOException { readMessageLength(); readMessage(); - final String message = buildMessage(); + final String message = buildMessage(); return message; } @@ -55,7 +56,7 @@ private void readMessageLength() throws IOException { private void readMessage() throws IOException { int remainder = nextMessageLength; - while (remainder > 0) { + while (remainder > 0) { final int bytesToRead = Math.min(remainder, messagePartBufferSize); final int n = inputStream.read(messagePartBuffer, 0, bytesToRead); messageBuffer.write(messagePartBuffer, 0, n); @@ -75,7 +76,7 @@ private void readBytesUntilNextSpace() throws IOException { if (b < 0) { throw new EOFException("The stream has been closed or the end of stream has been reached"); } - final byte currentByte = (byte)(b & 0xff); + final byte currentByte = (byte) (b & 0xff); if (currentByte == SPACE) { position = i; break; @@ -86,6 +87,6 @@ private void readBytesUntilNextSpace() throws IOException { private void calculateNextMessageLength() { final byte[] length = Arrays.copyOfRange(lengthBuffer, 0, position); - nextMessageLength = Integer.parseInt(new String(length)); + nextMessageLength = Integers.parseInt(new String(length)); } } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.java new file mode 100644 index 00000000000..9ce2b372498 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.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.net.ssl; + +import java.io.IOException; +import java.io.InputStream; + +public abstract class TlsSyslogInputStreamReaderBase { + + protected InputStream inputStream; + protected TlsSyslogMessageFormat messageFormat; + + protected TlsSyslogInputStreamReaderBase( + final InputStream inputStream, final TlsSyslogMessageFormat messageFormat) { + this.inputStream = inputStream; + this.messageFormat = messageFormat; + } + + public String read() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.java new file mode 100644 index 00000000000..855e9766f1d --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.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.test.net.ssl; + +public enum TlsSyslogMessageFormat { + LEGACY_BSD, + SYSLOG +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogTestUtil.java similarity index 85% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogTestUtil.java index b8dc2ea1d83..bb3b7691b63 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogTestUtil.java @@ -1,24 +1,26 @@ /* * 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.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.ArrayList; import java.util.Random; +@SuppressFBWarnings("PREDICTABLE_RANDOM") public class TlsSyslogTestUtil { public static final String ABC = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String NUMBERS = "0123456789"; @@ -72,4 +74,3 @@ public static int getRandomInt(final int max) { return n; } } - diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/package-info.java new file mode 100644 index 00000000000..97a787665d0 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/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. + */ +@Export +@Version("2.20.2") +package org.apache.logging.log4j.core.test.net.ssl; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/package-info.java new file mode 100644 index 00000000000..2199930f31f --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/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. + */ +@Export +@Version("2.25.0") +@BaselineIgnore("2.25.0") +package org.apache.logging.log4j.core.test; + +import aQute.bnd.annotation.baseline.BaselineIgnore; +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SimpleSmtpServer.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SimpleSmtpServer.java index e43811c9991..c02b115d22e 100644 --- a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SimpleSmtpServer.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SimpleSmtpServer.java @@ -1,21 +1,22 @@ /* * 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.logging.dumbster.smtp; +package org.apache.logging.log4j.core.test.smtp; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -25,7 +26,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; - import org.apache.logging.log4j.util.Strings; /** @@ -78,6 +78,7 @@ public SimpleSmtpServer(final int port) { * Main loop of the SMTP server. */ @Override + @SuppressFBWarnings("UNENCRYPTED_SERVER_SOCKET") public void run() { stopped = false; try { @@ -263,7 +264,6 @@ public static SimpleSmtpServer start(final int port) { final SimpleSmtpServer server = new SimpleSmtpServer(port); final Thread t = new Thread(server); - // Block until the server socket is created synchronized (server) { t.start(); @@ -275,5 +275,4 @@ public static SimpleSmtpServer start(final int port) { } return server; } - } diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpActionType.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpActionType.java index 9397ebb4b8f..9d3e52057e5 100644 --- a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpActionType.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpActionType.java @@ -1,202 +1,202 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.dumbster.smtp; - -/** - * Represents an SMTP action or command. - */ -public class SmtpActionType { - /** - * Internal value for the action type. - */ - private final byte value; - - /** - * Internal representation of the CONNECT action. - */ - private static final byte CONNECT_BYTE = (byte) 1; - /** - * Internal representation of the EHLO action. - */ - private static final byte EHLO_BYTE = (byte) 2; - /** - * Internal representation of the MAIL FROM action. - */ - private static final byte MAIL_BYTE = (byte) 3; - /** - * Internal representation of the RCPT action. - */ - private static final byte RCPT_BYTE = (byte) 4; - /** - * Internal representation of the DATA action. - */ - private static final byte DATA_BYTE = (byte) 5; - /** - * Internal representation of the DATA END (.) action. - */ - private static final byte DATA_END_BYTE = (byte) 6; - /** - * Internal representation of the QUIT action. - */ - private static final byte QUIT_BYTE = (byte) 7; - /** - * Internal representation of an unrecognized action: body text gets this action type. - */ - private static final byte UNREC_BYTE = (byte) 8; - /** - * Internal representation of the blank line action: separates headers and body text. - */ - private static final byte BLANK_LINE_BYTE = (byte) 9; - - /** - * Internal representation of the stateless RSET action. - */ - private static final byte RSET_BYTE = (byte) -1; - /** - * Internal representation of the stateless VRFY action. - */ - private static final byte VRFY_BYTE = (byte) -2; - /** - * Internal representation of the stateless EXPN action. - */ - private static final byte EXPN_BYTE = (byte) -3; - /** - * Internal representation of the stateless HELP action. - */ - private static final byte HELP_BYTE = (byte) -4; - /** - * Internal representation of the stateless NOOP action. - */ - private static final byte NOOP_BYTE = (byte) -5; - - /** - * CONNECT action. - */ - public static final SmtpActionType CONNECT = new SmtpActionType(CONNECT_BYTE); - /** - * EHLO action. - */ - public static final SmtpActionType EHLO = new SmtpActionType(EHLO_BYTE); - /** - * MAIL action. - */ - public static final SmtpActionType MAIL = new SmtpActionType(MAIL_BYTE); - /** - * RCPT action. - */ - public static final SmtpActionType RCPT = new SmtpActionType(RCPT_BYTE); - /** - * DATA action. - */ - public static final SmtpActionType DATA = new SmtpActionType(DATA_BYTE); - /** - * "." action. - */ - public static final SmtpActionType DATA_END = new SmtpActionType(DATA_END_BYTE); - /** - * Body text action. - */ - public static final SmtpActionType UNRECOG = new SmtpActionType(UNREC_BYTE); - /** - * QUIT action. - */ - public static final SmtpActionType QUIT = new SmtpActionType(QUIT_BYTE); - /** - * Header/body separator action. - */ - public static final SmtpActionType BLANK_LINE = new SmtpActionType(BLANK_LINE_BYTE); - - /** - * Stateless RSET action. - */ - public static final SmtpActionType RSET = new SmtpActionType(RSET_BYTE); - /** - * Stateless VRFY action. - */ - public static final SmtpActionType VRFY = new SmtpActionType(VRFY_BYTE); - /** - * Stateless EXPN action. - */ - public static final SmtpActionType EXPN = new SmtpActionType(EXPN_BYTE); - /** - * Stateless HELP action. - */ - public static final SmtpActionType HELP = new SmtpActionType(HELP_BYTE); - /** - * Stateless NOOP action. - */ - public static final SmtpActionType NOOP = new SmtpActionType(NOOP_BYTE); - - /** - * Create a new SMTP action type. Private to ensure no invalid values. - * - * @param value one of the _BYTE values - */ - private SmtpActionType(final byte value) { - this.value = value; - } - - /** - * Indicates whether the action is stateless or not. - * - * @return true iff the action is stateless - */ - public boolean isStateless() { - return value < 0; - } - - /** - * String representation of this SMTP action type. - * - * @return a String - */ - @Override - public String toString() { - switch (value) { - case CONNECT_BYTE: - return "Connect"; - case EHLO_BYTE: - return "EHLO"; - case MAIL_BYTE: - return "MAIL"; - case RCPT_BYTE: - return "RCPT"; - case DATA_BYTE: - return "DATA"; - case DATA_END_BYTE: - return "."; - case QUIT_BYTE: - return "QUIT"; - case RSET_BYTE: - return "RSET"; - case VRFY_BYTE: - return "VRFY"; - case EXPN_BYTE: - return "EXPN"; - case HELP_BYTE: - return "HELP"; - case NOOP_BYTE: - return "NOOP"; - case UNREC_BYTE: - return "Unrecognized command / data"; - case BLANK_LINE_BYTE: - return "Blank line"; - default: - return "Unknown"; - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smtp; + +/** + * Represents an SMTP action or command. + */ +public class SmtpActionType { + /** + * Internal value for the action type. + */ + private final byte value; + + /** + * Internal representation of the CONNECT action. + */ + private static final byte CONNECT_BYTE = (byte) 1; + /** + * Internal representation of the EHLO action. + */ + private static final byte EHLO_BYTE = (byte) 2; + /** + * Internal representation of the MAIL FROM action. + */ + private static final byte MAIL_BYTE = (byte) 3; + /** + * Internal representation of the RCPT action. + */ + private static final byte RCPT_BYTE = (byte) 4; + /** + * Internal representation of the DATA action. + */ + private static final byte DATA_BYTE = (byte) 5; + /** + * Internal representation of the DATA END (.) action. + */ + private static final byte DATA_END_BYTE = (byte) 6; + /** + * Internal representation of the QUIT action. + */ + private static final byte QUIT_BYTE = (byte) 7; + /** + * Internal representation of an unrecognized action: body text gets this action type. + */ + private static final byte UNREC_BYTE = (byte) 8; + /** + * Internal representation of the blank line action: separates headers and body text. + */ + private static final byte BLANK_LINE_BYTE = (byte) 9; + + /** + * Internal representation of the stateless RSET action. + */ + private static final byte RSET_BYTE = (byte) -1; + /** + * Internal representation of the stateless VRFY action. + */ + private static final byte VRFY_BYTE = (byte) -2; + /** + * Internal representation of the stateless EXPN action. + */ + private static final byte EXPN_BYTE = (byte) -3; + /** + * Internal representation of the stateless HELP action. + */ + private static final byte HELP_BYTE = (byte) -4; + /** + * Internal representation of the stateless NOOP action. + */ + private static final byte NOOP_BYTE = (byte) -5; + + /** + * CONNECT action. + */ + public static final SmtpActionType CONNECT = new SmtpActionType(CONNECT_BYTE); + /** + * EHLO action. + */ + public static final SmtpActionType EHLO = new SmtpActionType(EHLO_BYTE); + /** + * MAIL action. + */ + public static final SmtpActionType MAIL = new SmtpActionType(MAIL_BYTE); + /** + * RCPT action. + */ + public static final SmtpActionType RCPT = new SmtpActionType(RCPT_BYTE); + /** + * DATA action. + */ + public static final SmtpActionType DATA = new SmtpActionType(DATA_BYTE); + /** + * "." action. + */ + public static final SmtpActionType DATA_END = new SmtpActionType(DATA_END_BYTE); + /** + * Body text action. + */ + public static final SmtpActionType UNRECOG = new SmtpActionType(UNREC_BYTE); + /** + * QUIT action. + */ + public static final SmtpActionType QUIT = new SmtpActionType(QUIT_BYTE); + /** + * Header/body separator action. + */ + public static final SmtpActionType BLANK_LINE = new SmtpActionType(BLANK_LINE_BYTE); + + /** + * Stateless RSET action. + */ + public static final SmtpActionType RSET = new SmtpActionType(RSET_BYTE); + /** + * Stateless VRFY action. + */ + public static final SmtpActionType VRFY = new SmtpActionType(VRFY_BYTE); + /** + * Stateless EXPN action. + */ + public static final SmtpActionType EXPN = new SmtpActionType(EXPN_BYTE); + /** + * Stateless HELP action. + */ + public static final SmtpActionType HELP = new SmtpActionType(HELP_BYTE); + /** + * Stateless NOOP action. + */ + public static final SmtpActionType NOOP = new SmtpActionType(NOOP_BYTE); + + /** + * Create a new SMTP action type. Private to ensure no invalid values. + * + * @param value one of the _BYTE values + */ + private SmtpActionType(final byte value) { + this.value = value; + } + + /** + * Indicates whether the action is stateless or not. + * + * @return true iff the action is stateless + */ + public boolean isStateless() { + return value < 0; + } + + /** + * String representation of this SMTP action type. + * + * @return a String + */ + @Override + public String toString() { + switch (value) { + case CONNECT_BYTE: + return "Connect"; + case EHLO_BYTE: + return "EHLO"; + case MAIL_BYTE: + return "MAIL"; + case RCPT_BYTE: + return "RCPT"; + case DATA_BYTE: + return "DATA"; + case DATA_END_BYTE: + return "."; + case QUIT_BYTE: + return "QUIT"; + case RSET_BYTE: + return "RSET"; + case VRFY_BYTE: + return "VRFY"; + case EXPN_BYTE: + return "EXPN"; + case HELP_BYTE: + return "HELP"; + case NOOP_BYTE: + return "NOOP"; + case UNREC_BYTE: + return "Unrecognized command / data"; + case BLANK_LINE_BYTE: + return "Blank line"; + default: + return "Unknown"; + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpMessage.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpMessage.java index 967bb5e1944..cb9b8653c74 100644 --- a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpMessage.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpMessage.java @@ -1,20 +1,22 @@ /* * 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.logging.dumbster.smtp; +package org.apache.logging.log4j.core.test.smtp; + +import static org.apache.logging.log4j.util.Chars.LF; import java.util.ArrayList; import java.util.HashMap; @@ -22,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.logging.log4j.util.Strings; /** * Container for a complete SMTP message - headers and message body. @@ -84,9 +87,9 @@ public Iterator getHeaderNames() { public String[] getHeaderValues(final String name) { final List values = headers.get(name); if (values == null) { - return new String[0]; + return Strings.EMPTY_ARRAY; } - return values.toArray(new String[values.size()]); + return values.toArray(Strings.EMPTY_ARRAY); } /** @@ -143,12 +146,12 @@ public String toString() { msg.append(name); msg.append(": "); msg.append(value); - msg.append('\n'); + msg.append(LF); } } - msg.append('\n'); + msg.append(LF); msg.append(body); - msg.append('\n'); + msg.append(LF); return msg.toString(); } } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpRequest.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpRequest.java new file mode 100644 index 00000000000..29702484dcd --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpRequest.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.test.smtp; + +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + +import org.apache.logging.log4j.util.Strings; + +/** + * Contains an SMTP client request. Handles state transitions using the following state transition table. + *
+ * -----------+-------------------------------------------------------------------------------------------------
+ * |                                 State
+ * Action    +-------------+-----------+-----------+--------------+---------------+---------------+------------
+ * | CONNECT     | GREET     | MAIL      | RCPT         | DATA_HDR      | DATA_BODY     | QUIT
+ * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------
+ * connect    | 220/GREET   | 503/GREET | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
+ * ehlo       | 503/CONNECT | 250/MAIL  | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
+ * mail       | 503/CONNECT | 503/GREET | 250/RCPT  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 250/RCPT
+ * rcpt       | 503/CONNECT | 503/GREET | 503/MAIL  | 250/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
+ * data       | 503/CONNECT | 503/GREET | 503/MAIL  | 354/DATA_HDR | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
+ * data_end   | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | 250/QUIT      | 250/QUIT      | 503/QUIT
+ * unrecog    | 500/CONNECT | 500/GREET | 500/MAIL  | 500/RCPT     | ---/DATA_HDR  | ---/DATA_BODY | 500/QUIT
+ * quit       | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 250/CONNECT
+ * blank_line | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT
+ * rset       | 250/GREET   | 250/GREET | 250/GREET | 250/GREET    | 250/GREET     | 250/GREET     | 250/GREET
+ * vrfy       | 252/CONNECT | 252/GREET | 252/MAIL  | 252/RCPT     | 252/DATA_HDR  | 252/DATA_BODY | 252/QUIT
+ * expn       | 252/CONNECT | 252/GREET | 252/MAIL  | 252/RCPT     | 252/DATA_HDR  | 252/DATA_BODY | 252/QUIT
+ * help       | 211/CONNECT | 211/GREET | 211/MAIL  | 211/RCPT     | 211/DATA_HDR  | 211/DATA_BODY | 211/QUIT
+ * noop       | 250/CONNECT | 250/GREET | 250/MAIL  | 250/RCPT     | 250|DATA_HDR  | 250/DATA_BODY | 250/QUIT
+ * 
+ */ +public class SmtpRequest { + /** + * SMTP action received from client. + */ + private final SmtpActionType action; + /** + * Current state of the SMTP state table. + */ + private final SmtpState state; + /** + * Additional information passed from the client with the SMTP action. + */ + private final String params; + + /** + * Create a new SMTP client request. + * + * @param actionType type of action/command + * @param params remainder of command line once command is removed + * @param state current SMTP server state + */ + public SmtpRequest(final SmtpActionType actionType, final String params, final SmtpState state) { + this.action = actionType; + this.state = state; + this.params = params; + } + + /** + * Execute the SMTP request returning a response. This method models the state transition table for the SMTP server. + * + * @return reponse to the request + */ + public SmtpResponse execute() { + SmtpResponse response = null; + if (action.isStateless()) { + if (SmtpActionType.EXPN == action || SmtpActionType.VRFY == action) { + response = new SmtpResponse(252, "Not supported", this.state); + } else if (SmtpActionType.HELP == action) { + response = new SmtpResponse(211, "No help available", this.state); + } else if (SmtpActionType.NOOP == action) { + response = new SmtpResponse(250, "OK", this.state); + } else if (SmtpActionType.VRFY == action) { + response = new SmtpResponse(252, "Not supported", this.state); + } else if (SmtpActionType.RSET == action) { + response = new SmtpResponse(250, "OK", SmtpState.GREET); + } else { + response = new SmtpResponse(500, "Command not recognized", this.state); + } + // Stateful commands + } else if (SmtpActionType.CONNECT == action) { + if (SmtpState.CONNECT == state) { + response = new SmtpResponse(220, "localhost Dumbster SMTP service ready", SmtpState.GREET); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.EHLO == action) { + if (SmtpState.GREET == state) { + response = new SmtpResponse(250, "OK", SmtpState.MAIL); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.MAIL == action) { + if (SmtpState.MAIL == state || SmtpState.QUIT == state) { + response = new SmtpResponse(250, "OK", SmtpState.RCPT); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.RCPT == action) { + if (SmtpState.RCPT == state) { + response = new SmtpResponse(250, "OK", this.state); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.DATA == action) { + if (SmtpState.RCPT == state) { + response = new SmtpResponse(354, "Start mail input; end with .", SmtpState.DATA_HDR); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.UNRECOG == action) { + if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) { + response = new SmtpResponse(-1, Strings.EMPTY, this.state); + } else { + response = new SmtpResponse(500, "Command not recognized", this.state); + } + } else if (SmtpActionType.DATA_END == action) { + if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) { + response = new SmtpResponse(250, "OK", SmtpState.QUIT); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.BLANK_LINE == action) { + if (SmtpState.DATA_HDR == state) { + response = new SmtpResponse(-1, Strings.EMPTY, SmtpState.DATA_BODY); + } else if (SmtpState.DATA_BODY == state) { + response = new SmtpResponse(-1, Strings.EMPTY, this.state); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else if (SmtpActionType.QUIT == action) { + if (SmtpState.QUIT == state) { + response = new SmtpResponse( + 221, "localhost Dumbster service closing transmission channel", SmtpState.CONNECT); + } else { + response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); + } + } else { + response = new SmtpResponse(500, "Command not recognized", this.state); + } + return response; + } + + /** + * Create an SMTP request object given a line of the input stream from the client and the current internal state. + * + * @param s line of input + * @param state current state + * @return a populated SmtpRequest object + */ + public static SmtpRequest createRequest(final String s, final SmtpState state) { + SmtpActionType action = null; + String params = null; + + if (state == SmtpState.DATA_HDR) { + if (s.equals(".")) { + action = SmtpActionType.DATA_END; + } else if (s.length() < 1) { + action = SmtpActionType.BLANK_LINE; + } else { + action = SmtpActionType.UNRECOG; + params = s; + } + } else if (state == SmtpState.DATA_BODY) { + if (s.equals(".")) { + action = SmtpActionType.DATA_END; + } else { + action = SmtpActionType.UNRECOG; + if (s.length() < 1) { + params = "\n"; + } else { + params = s; + } + } + } else { + final String su = toRootUpperCase(s); + if (su.startsWith("EHLO ") || su.startsWith("HELO")) { + action = SmtpActionType.EHLO; + params = s.substring(5); + } else if (su.startsWith("MAIL FROM:")) { + action = SmtpActionType.MAIL; + params = s.substring(10); + } else if (su.startsWith("RCPT TO:")) { + action = SmtpActionType.RCPT; + params = s.substring(8); + } else if (su.startsWith("DATA")) { + action = SmtpActionType.DATA; + } else if (su.startsWith("QUIT")) { + action = SmtpActionType.QUIT; + } else if (su.startsWith("RSET")) { + action = SmtpActionType.RSET; + } else if (su.startsWith("NOOP")) { + action = SmtpActionType.NOOP; + } else if (su.startsWith("EXPN")) { + action = SmtpActionType.EXPN; + } else if (su.startsWith("VRFY")) { + action = SmtpActionType.VRFY; + } else if (su.startsWith("HELP")) { + action = SmtpActionType.HELP; + } else { + action = SmtpActionType.UNRECOG; + } + } + + final SmtpRequest req = new SmtpRequest(action, params, state); + return req; + } + + /** + * Get the parameters of this request (remainder of command line once the command is removed. + * + * @return parameters + */ + public String getParams() { + return params; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpResponse.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpResponse.java index 7023f1c39f2..b33513d197a 100644 --- a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpResponse.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpResponse.java @@ -1,75 +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.dumbster.smtp; - -/** - * SMTP response container. - */ -public class SmtpResponse { - /** - * Response code - see RFC-2821. - */ - private final int code; - /** - * Response message. - */ - private final String message; - /** - * New state of the SMTP server once the request has been executed. - */ - private final SmtpState nextState; - - /** - * Constructor. - * - * @param code response code - * @param message response message - * @param next next state of the SMTP server - */ - public SmtpResponse(final int code, final String message, final SmtpState next) { - this.code = code; - this.message = message; - this.nextState = next; - } - - /** - * Get the response code. - * - * @return response code - */ - public int getCode() { - return code; - } - - /** - * Get the response message. - * - * @return response message - */ - public String getMessage() { - return message; - } - - /** - * Get the next SMTP server state. - * - * @return state - */ - public SmtpState getNextState() { - return nextState; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smtp; + +/** + * SMTP response container. + */ +public class SmtpResponse { + /** + * Response code - see RFC-2821. + */ + private final int code; + /** + * Response message. + */ + private final String message; + /** + * New state of the SMTP server once the request has been executed. + */ + private final SmtpState nextState; + + /** + * Constructor. + * + * @param code response code + * @param message response message + * @param next next state of the SMTP server + */ + public SmtpResponse(final int code, final String message, final SmtpState next) { + this.code = code; + this.message = message; + this.nextState = next; + } + + /** + * Get the response code. + * + * @return response code + */ + public int getCode() { + return code; + } + + /** + * Get the response message. + * + * @return response message + */ + public String getMessage() { + return message; + } + + /** + * Get the next SMTP server state. + * + * @return state + */ + public SmtpState getNextState() { + return nextState; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpState.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpState.java index bd94b4d1431..17edf0c81e9 100644 --- a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/SmtpState.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/SmtpState.java @@ -1,121 +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.dumbster.smtp; - -/** - * SMTP server state. - */ -public class SmtpState { - /** - * Internal representation of the state. - */ - private final byte value; - - /** - * Internal representation of the CONNECT state. - */ - private static final byte CONNECT_BYTE = (byte) 1; - /** - * Internal representation of the GREET state. - */ - private static final byte GREET_BYTE = (byte) 2; - /** - * Internal representation of the MAIL state. - */ - private static final byte MAIL_BYTE = (byte) 3; - /** - * Internal representation of the RCPT state. - */ - private static final byte RCPT_BYTE = (byte) 4; - /** - * Internal representation of the DATA_HEADER state. - */ - private static final byte DATA_HEADER_BYTE = (byte) 5; - /** - * Internal representation of the DATA_BODY state. - */ - private static final byte DATA_BODY_BYTE = (byte) 6; - /** - * Internal representation of the QUIT state. - */ - private static final byte QUIT_BYTE = (byte) 7; - - /** - * CONNECT state: waiting for a client connection. - */ - public static final SmtpState CONNECT = new SmtpState(CONNECT_BYTE); - /** - * GREET state: wating for a ELHO message. - */ - public static final SmtpState GREET = new SmtpState(GREET_BYTE); - /** - * MAIL state: waiting for the MAIL FROM: command. - */ - public static final SmtpState MAIL = new SmtpState(MAIL_BYTE); - /** - * RCPT state: waiting for a RCPT <email address> command. - */ - public static final SmtpState RCPT = new SmtpState(RCPT_BYTE); - /** - * Waiting for headers. - */ - public static final SmtpState DATA_HDR = new SmtpState(DATA_HEADER_BYTE); - /** - * Processing body text. - */ - public static final SmtpState DATA_BODY = new SmtpState(DATA_BODY_BYTE); - /** - * End of client transmission. - */ - public static final SmtpState QUIT = new SmtpState(QUIT_BYTE); - - /** - * Create a new SmtpState object. Private to ensure that only valid states can be created. - * - * @param value one of the _BYTE values. - */ - private SmtpState(final byte value) { - this.value = value; - } - - /** - * String representation of this SmtpState. - * - * @return a String - */ - @Override - public String toString() { - switch (value) { - case CONNECT_BYTE: - return "CONNECT"; - case GREET_BYTE: - return "GREET"; - case MAIL_BYTE: - return "MAIL"; - case RCPT_BYTE: - return "RCPT"; - case DATA_HEADER_BYTE: - return "DATA_HDR"; - case DATA_BODY_BYTE: - return "DATA_BODY"; - case QUIT_BYTE: - return "QUIT"; - default: - return "Unknown"; - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.smtp; + +/** + * SMTP server state. + */ +public class SmtpState { + /** + * Internal representation of the state. + */ + private final byte value; + + /** + * Internal representation of the CONNECT state. + */ + private static final byte CONNECT_BYTE = (byte) 1; + /** + * Internal representation of the GREET state. + */ + private static final byte GREET_BYTE = (byte) 2; + /** + * Internal representation of the MAIL state. + */ + private static final byte MAIL_BYTE = (byte) 3; + /** + * Internal representation of the RCPT state. + */ + private static final byte RCPT_BYTE = (byte) 4; + /** + * Internal representation of the DATA_HEADER state. + */ + private static final byte DATA_HEADER_BYTE = (byte) 5; + /** + * Internal representation of the DATA_BODY state. + */ + private static final byte DATA_BODY_BYTE = (byte) 6; + /** + * Internal representation of the QUIT state. + */ + private static final byte QUIT_BYTE = (byte) 7; + + /** + * CONNECT state: waiting for a client connection. + */ + public static final SmtpState CONNECT = new SmtpState(CONNECT_BYTE); + /** + * GREET state: wating for a ELHO message. + */ + public static final SmtpState GREET = new SmtpState(GREET_BYTE); + /** + * MAIL state: waiting for the MAIL FROM: command. + */ + public static final SmtpState MAIL = new SmtpState(MAIL_BYTE); + /** + * RCPT state: waiting for a RCPT <email address> command. + */ + public static final SmtpState RCPT = new SmtpState(RCPT_BYTE); + /** + * Waiting for headers. + */ + public static final SmtpState DATA_HDR = new SmtpState(DATA_HEADER_BYTE); + /** + * Processing body text. + */ + public static final SmtpState DATA_BODY = new SmtpState(DATA_BODY_BYTE); + /** + * End of client transmission. + */ + public static final SmtpState QUIT = new SmtpState(QUIT_BYTE); + + /** + * Create a new SmtpState object. Private to ensure that only valid states can be created. + * + * @param value one of the _BYTE values. + */ + private SmtpState(final byte value) { + this.value = value; + } + + /** + * String representation of this SmtpState. + * + * @return a String + */ + @Override + public String toString() { + switch (value) { + case CONNECT_BYTE: + return "CONNECT"; + case GREET_BYTE: + return "GREET"; + case MAIL_BYTE: + return "MAIL"; + case RCPT_BYTE: + return "RCPT"; + case DATA_HEADER_BYTE: + return "DATA_HDR"; + case DATA_BODY_BYTE: + return "DATA_BODY"; + case QUIT_BYTE: + return "QUIT"; + default: + return "Unknown"; + } + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/package-info.java new file mode 100644 index 00000000000..3d4e74b7a9a --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/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. + */ +@Export +@Version("2.20.2") +package org.apache.logging.log4j.core.test.smtp; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/test/java/org/apache/logging/dumbster/smtp/readme.txt b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/readme.txt similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/dumbster/smtp/readme.txt rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/smtp/readme.txt diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.java new file mode 100644 index 00000000000..319e270fec8 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.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.util; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.Strings; + +/** + * YourKit Java Profiler helper class. + */ +public final class Profiler { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private static Object profiler; + private static Class profilingModes; + private static Class controllerClazz; + + static { + try { + controllerClazz = LoaderUtil.loadClass("com.yourkit.api.Controller"); + profilingModes = LoaderUtil.loadClass("com.yourkit.api.ProfilingModes"); + try { + profiler = controllerClazz.getConstructor().newInstance(); + } catch (final Exception e) { + LOGGER.error("Profiler was active, but failed.", e); + } + } catch (final Exception ignored) { + // Ignore + } + } + + private Profiler() {} + + public static boolean isActive() { + return profiler != null; + } + + private static long cpuSampling() throws NoSuchFieldException, IllegalAccessException { + return profilingModes.getDeclaredField("CPU_SAMPLING").getLong(profilingModes); + } + + private static long snapshotWithoutHeap() throws NoSuchFieldException, IllegalAccessException { + return profilingModes.getDeclaredField("SNAPSHOT_WITHOUT_HEAP").getLong(profilingModes); + } + + public static void start() { + + if (profiler != null) { + try { + controllerClazz + .getMethod("startCPUProfiling", long.class, String.class) + .invoke(profiler, cpuSampling(), Strings.EMPTY); + } catch (final Exception e) { + LOGGER.error("Profiler was active, but failed.", e); + } + } + } + + public static void stop() { + if (profiler != null) { + try { + controllerClazz.getMethod("captureSnapshot", long.class).invoke(profiler, snapshotWithoutHeap()); + controllerClazz.getMethod("stopCPUProfiling").invoke(profiler); + } catch (final Exception e) { + LOGGER.error("Profiler was active, but failed.", e); + } + } + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/package-info.java new file mode 100644 index 00000000000..21c35b60f0f --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/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. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.test.util; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..c44e6903460 --- /dev/null +++ b/log4j-core-test/src/main/resources/log4j2-calling-class.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/main/resources/log4j2-config.xml b/log4j-core-test/src/main/resources/log4j2-config.xml new file mode 100644 index 00000000000..d247bd15cd6 --- /dev/null +++ b/log4j-core-test/src/main/resources/log4j2-config.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/java/foo/TestFriendlyException.java b/log4j-core-test/src/test/java/foo/TestFriendlyException.java new file mode 100644 index 00000000000..7c791dc20d2 --- /dev/null +++ b/log4j-core-test/src/test/java/foo/TestFriendlyException.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 foo; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.Socket; +import java.util.Arrays; +import java.util.stream.Stream; +import org.apache.logging.log4j.util.Constants; + +/** + * A testing friendly exception featuring + *
    + *
  • Distinct localized message
  • + *
  • Non-Log4j1 and fixed2 (to {@code bar}) package origin
  • + *
  • Sufficient causal chain depth
  • + *
  • Circular causal chain
  • + *
  • Suppressed exceptions
  • + *
  • Clutter-free stack trace (i.e., elements from JUnit, JDK, etc.)
  • + *
  • Stack trace elements from named modules3
  • + *
+ *

+ * 1 Helps with observing stack trace manipulation effects of Log4j. + *

+ *

+ * 2 Helps to make the origin of {@link #INSTANCE} independent of the first test accessing to it. + *

+ *

+ * 3 Helps with testing module name serialization. + *

+ */ +public final class TestFriendlyException extends RuntimeException { + + static { + // Ensure the distinct packaging + assertThat(TestFriendlyException.class.getPackage().getName()).doesNotStartWith("org.apache"); + } + + public static final StackTraceElement ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT = + new StackTraceElement("bar.OrgApacheReplacement", "someMethod", "OrgApacheReplacement.java", 0); + + public static final StackTraceElement NAMED_MODULE_STACK_TRACE_ELEMENT = namedModuleStackTraceElement(); + + @SuppressWarnings("resource") + private static StackTraceElement namedModuleStackTraceElement() { + try { + new Socket("0.0.0.0", -1); + } catch (final Exception error) { + final StackTraceElement[] stackTraceElements = error.getStackTrace(); + final String socketClassName = Socket.class.getCanonicalName(); + for (final StackTraceElement stackTraceElement : stackTraceElements) { + if (stackTraceElement.getClassName().equals(socketClassName)) { + if (Constants.JAVA_MAJOR_VERSION > 8) { + final String stackTraceElementString = stackTraceElement.toString(); + assertThat(stackTraceElementString).startsWith("java.base/"); + } + return stackTraceElement; + } + } + } + throw new IllegalStateException("should not have reached here"); + } + + private static final String[] EXCLUDED_CLASS_NAME_PREFIXES = { + "java.lang", "jdk.internal", "org.junit", "sun.reflect" + }; + + public static final TestFriendlyException INSTANCE = create("r", 0, 2, new boolean[] {false}, new boolean[] {true}); + + private static TestFriendlyException create( + final String name, + final int depth, + final int maxDepth, + final boolean[] circular, + final boolean[] namedModuleAllowed) { + final TestFriendlyException error = new TestFriendlyException(name, namedModuleAllowed); + if (depth < maxDepth) { + final TestFriendlyException cause = create(name + "_c", depth + 1, maxDepth, circular, namedModuleAllowed); + error.initCause(cause); + final TestFriendlyException suppressed = + create(name + "_s", depth + 1, maxDepth, circular, namedModuleAllowed); + error.addSuppressed(suppressed); + final boolean circularAllowed = depth + 1 == maxDepth && !circular[0]; + if (circularAllowed) { + cause.initCause(error); + suppressed.initCause(error); + circular[0] = true; + } + } + return error; + } + + private TestFriendlyException(final String message, final boolean[] namedModuleAllowed) { + super(message); + removeExcludedStackTraceElements(namedModuleAllowed); + } + + private void removeExcludedStackTraceElements(final boolean[] namedModuleAllowed) { + final StackTraceElement[] oldStackTrace = getStackTrace(); + final boolean[] seenExcludedStackTraceElement = {false}; + final StackTraceElement[] newStackTrace = Arrays.stream(oldStackTrace) + .flatMap(stackTraceElement -> + mapStackTraceElement(stackTraceElement, namedModuleAllowed, seenExcludedStackTraceElement)) + .toArray(StackTraceElement[]::new); + setStackTrace(newStackTrace); + } + + private static Stream mapStackTraceElement( + final StackTraceElement stackTraceElement, + final boolean[] namedModuleAllowed, + final boolean[] seenExcludedStackTraceElement) { + final Stream filteredStackTraceElement = + filterStackTraceElement(stackTraceElement, seenExcludedStackTraceElement); + final Stream javaBaseIncludedStackTraceElement = + namedModuleIncludedStackTraceElement(namedModuleAllowed); + return Stream.concat(javaBaseIncludedStackTraceElement, filteredStackTraceElement); + } + + private static Stream filterStackTraceElement( + final StackTraceElement stackTraceElement, final boolean[] seenExcludedStackTraceElement) { + + // Short-circuit if we have already encountered an excluded stack trace element + if (seenExcludedStackTraceElement[0]) { + return Stream.empty(); + } + + // Check if the class name is excluded + final String className = stackTraceElement.getClassName(); + for (final String excludedClassNamePrefix : EXCLUDED_CLASS_NAME_PREFIXES) { + if (className.startsWith(excludedClassNamePrefix)) { + seenExcludedStackTraceElement[0] = true; + return Stream.empty(); + } + } + + // Replace `org.apache`-originating entries with a constant one. + // Without this, `INSTANCE` might yield different origin depending on the first class accessing to it. + // We remove this ambiguity and fix our origin to a constant instead. + if (className.startsWith("org.apache")) { + return Stream.of(ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT); + } + + // Otherwise, it looks good + return Stream.of(stackTraceElement); + } + + private static Stream namedModuleIncludedStackTraceElement(final boolean[] namedModuleAllowed) { + if (!namedModuleAllowed[0]) { + return Stream.of(); + } + namedModuleAllowed[0] = false; + return Stream.of(NAMED_MODULE_STACK_TRACE_ELEMENT); + } + + @Override + public String getLocalizedMessage() { + return getMessage() + " [localized]"; + } +} 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/FormatterLoggerManualExample.java similarity index 77% 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/FormatterLoggerManualExample.java index d5a14389d98..0f12ad168f4 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/FormatterLoggerManualExample.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/FormatterLoggerManualExample.java @@ -1,24 +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. */ package org.apache.logging.log4j; import java.util.Calendar; import java.util.GregorianCalendar; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; @@ -40,15 +39,13 @@ String getName() { * @param args */ public static void main(final String[] args) { - try (final LoggerContext ctx = Configurator.initialize(FormatterLoggerManualExample.class.getName(), - "target/test-classes/log4j2-console.xml");) { + try (final LoggerContext ctx = Configurator.initialize( + FormatterLoggerManualExample.class.getName(), "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()); logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE); logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE); } - } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/LogRolloverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/LogRolloverTest.java new file mode 100644 index 00000000000..700167ccd0e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/LogRolloverTest.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; + +import java.io.File; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; + +/** + * + */ +public class LogRolloverTest { + + private static final String CONFIG = "src/test/resources/rollover-test.xml"; + + public static void main(final String[] args) throws Exception { + final File file = new File(CONFIG); + try (final LoggerContext ctx = + Configurator.initialize("LogTest", LogRolloverTest.class.getClassLoader(), file.toURI())) { + final Logger logger = LogManager.getLogger("TestLogger"); + + for (long i = 0; ; i += 1) { + logger.debug("Sequence: " + i); + Thread.sleep(250); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInJsonTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInJsonTest.java new file mode 100644 index 00000000000..07e10805d4d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInJsonTest.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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; +import org.junit.jupiter.api.Tag; + +@Tag("Layouts.Json") +class MarkerMixInJsonTest extends MarkerMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jJsonObjectMapper(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInTest.java new file mode 100644 index 00000000000..00175a393f6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import java.io.IOException; +import org.apache.logging.log4j.MarkerManager.Log4jMarker; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link MarkerMixIn}. + * + * This class is in this package to let {@link Log4jMarker} have the least visibility. + */ +abstract class MarkerMixInTest { + + private ObjectReader reader; + private ObjectWriter writer; + + @BeforeEach + void setUp() { + final ObjectMapper log4jObjectMapper = newObjectMapper(); + writer = log4jObjectMapper.writer(); + reader = log4jObjectMapper.readerFor(Log4jMarker.class); + MarkerManager.clear(); + } + + protected abstract ObjectMapper newObjectMapper(); + + @Test + void testNameOnly() throws IOException { + final Marker expected = MarkerManager.getMarker("A"); + final String str = writeValueAsString(expected); + assertFalse(str.contains("parents")); + final Marker actual = reader.readValue(str); + assertEquals(expected, actual); + } + + @Test + void testOneParent() throws IOException { + final Marker expected = MarkerManager.getMarker("A"); + final Marker parent = MarkerManager.getMarker("PARENT_MARKER"); + expected.addParents(parent); + final String str = writeValueAsString(expected); + assertTrue(str.contains("PARENT_MARKER")); + final Marker actual = reader.readValue(str); + assertEquals(expected, actual); + } + + /** + * @param expected + * @return + * @throws JsonProcessingException + */ + private String writeValueAsString(final Marker expected) throws JsonProcessingException { + final String str = writer.writeValueAsString(expected); + // System.out.println(str); + return str; + } + + @Test + void testTwoParents() throws IOException { + final Marker expected = MarkerManager.getMarker("A"); + final Marker parent1 = MarkerManager.getMarker("PARENT_MARKER1"); + final Marker parent2 = MarkerManager.getMarker("PARENT_MARKER2"); + expected.addParents(parent1); + expected.addParents(parent2); + final String str = writeValueAsString(expected); + assertTrue(str.contains("PARENT_MARKER1")); + assertTrue(str.contains("PARENT_MARKER2")); + final Marker actual = reader.readValue(str); + assertEquals(expected, actual); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java new file mode 100644 index 00000000000..70aa1e5a44a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper; +import org.junit.jupiter.api.Tag; + +@Tag("Layouts.Xml") +class MarkerMixInXmlTest extends MarkerMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jXmlObjectMapper(true, false); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.java new file mode 100644 index 00000000000..1f9297fc301 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/MarkerMixInYamlTest.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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper; +import org.junit.jupiter.api.Tag; + +@Tag("Layouts.Yaml") +class MarkerMixInYamlTest extends MarkerMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jYamlObjectMapper(); + } +} 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..fbbcf7b1e31 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelJsonTest.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; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +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; + +@Tag("json") +@LoggerContextSource("log4j-reference-level.json") +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 + 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 + 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 + 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..f47f2cdf6c6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +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; + +@LoggerContextSource("log4j-reference-level.xml") +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 + 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 + 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(2, events.size(), "Incorrect number of events. Expected 2, actual " + events.size()); + events = app2.getEvents(); + assertEquals(4, events.size(), "Incorrect number of events. Expected 4, actual " + events.size()); + } + + @Test + 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-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java new file mode 100644 index 00000000000..12fd307d276 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.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.core; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("smoke") +class BasicLoggingTest { + + @Test + void test1() { + final Logger logger = LogManager.getLogger(BasicLoggingTest.class.getName()); + logger.debug("debug not set"); + logger.error("Test message"); + } +} 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..f29bb5d61ca --- /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 java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +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.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +@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 + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + 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 + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + 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 + void testNetworkInterfaces(final LoggerContext context) throws SocketException { + final Logger logger = context.getLogger(CollectionLoggingTest.class.getName()); + logger.error(NetworkInterface.getNetworkInterfaces()); + // TODO: some assertions + } + + @Test + 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 75% 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..7b2978ef190 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; @@ -24,7 +24,6 @@ */ public class CronRolloverApp { - private static Logger logger; public static void main(final String[] args) { @@ -36,8 +35,8 @@ public static void main(final String[] args) { Thread.sleep(1 * 1000); } } catch (final Exception e) { - //e.printStackTrace(); - logger.error("Excepcion general", e); + // e.printStackTrace(); + 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..8bcbb90ce04 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.logging.log4j.Level; +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; + +@LoggerContextSource("log4j-customLevels.xml") +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 + 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 + void testCustomLevelPresence() { + assertNotNull(warnLevel); + assertNotNull(infoLevel); + assertNotNull(debugLevel); + } + + @Test + void testCustomLevelVsStdLevel() { + assertEquals(Level.WARN, warnLevel); + assertEquals(Level.INFO, infoLevel); + assertEquals(Level.DEBUG, debugLevel); + } + + @Test + 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..fb32835d2be --- /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.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.logging.log4j.Level; +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; + +@LoggerContextSource("log4j-customLevels.xml") +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 + void testCustomLevelInts() { + assertEquals(350, diagLevel.intLevel()); + assertEquals(450, noticeLevel.intLevel()); + assertEquals(550, verboseLevel.intLevel()); + } + + @Test + void testCustomLevelPresence() { + assertNotNull(diagLevel); + assertNotNull(noticeLevel); + assertNotNull(verboseLevel); + } + + @Test + 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-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java new file mode 100644 index 00000000000..f4bcacdce03 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.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; + +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 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.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-customLevelsWithFilters.xml") +class CustomLevelsWithFiltersTest { + + private Level infom1Level; + private Level infop1Level; + + @BeforeEach + void before() { + infom1Level = Level.getLevel("INFOM1"); + infop1Level = Level.getLevel("INFOP1"); + } + + @Test + void testConfiguration(final Configuration configuration, @Named("info") final FileAppender appender) { + assertNotNull(configuration); + assertNotNull(appender); + final CompositeFilter compFilter = (CompositeFilter) appender.getFilter(); + assertNotNull(compFilter); + final Filter[] filters = compFilter.getFiltersArray(); + assertNotNull(filters); + boolean foundLevel = false; + for (final Filter filter : filters) { + final ThresholdFilter tFilter = (ThresholdFilter) filter; + if (infom1Level.equals(tFilter.getLevel())) { + foundLevel = true; + break; + } + } + assertTrue(foundLevel, "Level not found: " + infom1Level); + } + + @Test + void testCustomLevelInts() { + assertEquals(399, infom1Level.intLevel()); + assertEquals(401, infop1Level.intLevel()); + } + + @Test + void testCustomLevelPresence() { + assertNotNull(infom1Level); + assertNotNull(infop1Level); + } +} 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..87a2c9c9ec7 --- /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") +class DeadlockTest { + + @Test + 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..ff08d2b2cf8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/EventParameterMemoryLeakTest.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 static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; +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.api.RootLoggerComponentBuilder; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +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.TestInfo; +import org.junitpioneer.jupiter.SetSystemProperty; + +@Tag("functional") +class EventParameterMemoryLeakTest { + + @Test + @SetSystemProperty(key = "log4j2.enableDirectEncoders", value = "true") + @SetSystemProperty(key = "log4j2.enableThreadLocals", value = "true") + void parameters_should_be_garbage_collected(final TestInfo testInfo) throws Throwable { + awaitGarbageCollection(() -> { + final ListAppender[] appenderRef = {null}; + final Logger[] loggerRef = {null}; + try (final LoggerContext ignored = createLoggerContext(testInfo, appenderRef, loggerRef)) { + + // Log messages + final ParameterObject parameter = new ParameterObject("paramValue"); + loggerRef[0].info("Message with parameter {}", parameter); + loggerRef[0].info(parameter); + loggerRef[0].info("test", new ObjectThrowable(parameter)); + loggerRef[0].info("test {}", "hello", new ObjectThrowable(parameter)); + + // Verify the logging + final List messages = appenderRef[0].getMessages(); + assertThat(messages).hasSize(4); + assertThat(messages.get(0)).isEqualTo("Message with parameter %s", parameter.value); + assertThat(messages.get(1)).isEqualTo(parameter.value); + assertThat(messages.get(2)) + .startsWith(String.format("test%n%s: %s", ObjectThrowable.class.getName(), parameter.value)); + assertThat(messages.get(3)) + .startsWith( + String.format("test hello%n%s: %s", ObjectThrowable.class.getName(), parameter.value)); + + // Return the GC subject + return parameter; + } + }); + } + + private static LoggerContext createLoggerContext( + final TestInfo testInfo, final ListAppender[] appenderRef, final Logger[] loggerRef) { + final String loggerContextName = String.format("%s-LC", testInfo.getDisplayName()); + final LoggerContext loggerContext = new LoggerContext(loggerContextName); + final String appenderName = "LIST"; + final Configuration configuration = createConfiguration(appenderName); + loggerContext.start(configuration); + appenderRef[0] = configuration.getAppender(appenderName); + assertThat(appenderRef[0]).isNotNull(); + final Class testClass = testInfo.getTestClass().orElse(null); + assertThat(testClass).isNotNull(); + loggerRef[0] = loggerContext.getLogger(testClass); + return loggerContext; + } + + @SuppressWarnings("SameParameterValue") + private static Configuration createConfiguration(final String appenderName) { + final ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + final LayoutComponentBuilder layoutComponentBuilder = + configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m"); + final AppenderComponentBuilder appenderComponentBuilder = + configBuilder.newAppender(appenderName, "List").add(layoutComponentBuilder); + final RootLoggerComponentBuilder loggerComponentBuilder = + configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(appenderName)); + return configBuilder + .add(appenderComponentBuilder) + .add(loggerComponentBuilder) + .build(false); + } + + private static final class ParameterObject { + + private final String value; + + private ParameterObject(final String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + + private static final class ObjectThrowable extends RuntimeException { + + private final Object object; + + private ObjectThrowable(final 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..8e30e081dea --- /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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.ExtendedLevels; +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; + +@LoggerContextSource("log4j-customLevel.xml") +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 + 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-test/src/test/java/org/apache/logging/log4j/core/GcFreeAsynchronousLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeAsynchronousLoggingTest.java new file mode 100644 index 00000000000..8cbcb9f986d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeAsynchronousLoggingTest.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 org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.test.GcFreeLoggingTestUtil; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Verifies steady state logging is GC-free. + * + * @see https://github.com/google/allocation-instrumenter + */ +@Tag("allocation") +@Tag("functional") +class GcFreeAsynchronousLoggingTest { + + @Test + void testNoAllocationDuringSteadyStateLogging() throws Throwable { + GcFreeLoggingTestUtil.runTest(getClass()); + } + + /** + * This code runs in a separate process, instrumented with the Google Allocation Instrumenter. + */ + public static void main(final String[] args) throws Exception { + System.setProperty("log4j2.garbagefree.threadContextMap", "true"); + System.setProperty("AsyncLogger.RingBufferSize", "128"); // minimum ringbuffer size + System.setProperty("Log4jContextSelector", AsyncLoggerContextSelector.class.getName()); + GcFreeLoggingTestUtil.executeLogging("gcFreeLogging.xml", GcFreeAsynchronousLoggingTest.class); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeMixedSyncAsyncLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeMixedSyncAsyncLoggingTest.java new file mode 100644 index 00000000000..b540fe67151 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeMixedSyncAsyncLoggingTest.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; + +import org.apache.logging.log4j.core.test.GcFreeLoggingTestUtil; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Verifies steady state mixed synchronous and asynchronous logging is GC-free. + * + * @see https://github.com/google/allocation-instrumenter + */ +@Tag("allocation") +@Tag("functional") +class GcFreeMixedSyncAsyncLoggingTest { + + @Test + void testNoAllocationDuringSteadyStateLogging() throws Throwable { + GcFreeLoggingTestUtil.runTest(getClass()); + } + + /** + * This code runs in a separate process, instrumented with the Google Allocation Instrumenter. + */ + public static void main(final String[] args) throws Exception { + System.setProperty("log4j2.garbagefree.threadContextMap", "true"); + System.setProperty("AsyncLoggerConfig.RingBufferSize", "128"); // minimum ringbuffer size + GcFreeLoggingTestUtil.executeLogging("gcFreeMixedSyncAsyncLogging.xml", GcFreeMixedSyncAsyncLoggingTest.class); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeSynchronousLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeSynchronousLoggingTest.java new file mode 100644 index 00000000000..4d61acc5ace --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcFreeSynchronousLoggingTest.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; + +import org.apache.logging.log4j.core.test.GcFreeLoggingTestUtil; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Verifies steady state synchronous logging is GC-free. + * + * @see https://github.com/google/allocation-instrumenter + */ +@Tag("allocation") +@Tag("functional") +class GcFreeSynchronousLoggingTest { + + @Test + void testNoAllocationDuringSteadyStateLogging() throws Throwable { + GcFreeLoggingTestUtil.runTest(getClass()); + } + + /** + * This code runs in a separate process, instrumented with the Google Allocation Instrumenter. + */ + public static void main(final String[] args) throws Exception { + System.setProperty("log4j2.garbagefree.threadContextMap", "true"); + GcFreeLoggingTestUtil.executeLogging("gcFreeLogging.xml", GcFreeSynchronousLoggingTest.class); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelper.java new file mode 100644 index 00000000000..f77f87d12c3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelper.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; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import org.junit.jupiter.api.function.ThrowingSupplier; + +public final class GcHelper { + + private GcHelper() {} + + /** + * Waits for the value to be garbage collected. + * + * @param valueSupplier a value provider + */ + @SuppressWarnings({"unused", "UnusedAssignment"}) + public static void awaitGarbageCollection(final ThrowingSupplier valueSupplier) throws InterruptedException { + + // Create the reference queue + final ReferenceQueue refQueue = new ReferenceQueue<>(); + final Reference ref; + try { + final Object value = valueSupplier.get(); + ref = new PhantomReference<>(value, refQueue); + } catch (final Throwable error) { + throw new RuntimeException("failed obtaining value", error); + } + + // Wait for the garbage collection + try (final GcPressureGenerator ignored = GcPressureGenerator.ofStarted()) { + final Reference removedRef = refQueue.remove(30_000L); + if (removedRef == null) { + throw new AssertionError("garbage collector did not reclaim the value"); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelperTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelperTest.java new file mode 100644 index 00000000000..0c5cd951654 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcHelperTest.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; + +import static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; + +import org.junit.jupiter.api.Test; + +class GcHelperTest { + + @Test + void await_should_work() throws InterruptedException { + awaitGarbageCollection(Object::new); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcPressureGenerator.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcPressureGenerator.java new file mode 100644 index 00000000000..c45c44a54f3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GcPressureGenerator.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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Creates GC pressure by continuously allocating and {@link System#gc() triggering GC}. + */ +final class GcPressureGenerator implements AutoCloseable { + + private final AtomicInteger sink = new AtomicInteger(0); + + private final AtomicBoolean running = new AtomicBoolean(true); + + private final CountDownLatch stopLatch = new CountDownLatch(1); + + private GcPressureGenerator() { + startGeneratorThread(); + } + + private void startGeneratorThread() { + final String threadName = GcPressureGenerator.class.getSimpleName(); + final Thread thread = new Thread(this::generateGarbage, threadName); + thread.setDaemon(true); // Avoid blocking JVM exit + thread.start(); + } + + private void generateGarbage() { + try { + while (running.get()) { + final Object object = new byte[1024 * 1024]; + int positiveValue = Math.abs(object.hashCode()); + sink.set(positiveValue); + System.gc(); + System.runFinalization(); + } + } finally { + stopLatch.countDown(); + } + } + + static GcPressureGenerator ofStarted() { + return new GcPressureGenerator(); + } + + @Override + public void close() { + final boolean signalled = running.compareAndSet(true, false); + if (signalled) { + try { + final boolean stopped = stopLatch.await(10, TimeUnit.SECONDS); + assertThat(stopped).isTrue(); + assertThat(sink.get()).isPositive(); + } catch (final InterruptedException error) { + // Restore the `interrupted` flag + Thread.currentThread().interrupt(); + throw new RuntimeException(error); + } + } + } +} 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..af904074a10 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/HostNameTest.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; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +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.util.NetUtils; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-test2.xml") +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 + 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..20b574e8a41 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LateConfigTest.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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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 java.net.URI; +import java.nio.file.Path; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class LateConfigTest { + + private static final String CONFIG = "/log4j-test1.xml"; + private static LoggerContext context; + + @TempLoggingDir + private static Path loggingPath; + + @BeforeAll + static void setupClass() { + context = LoggerContext.getContext(false); + } + + @AfterAll + static void tearDownClass() { + Configurator.shutdown(context); + StatusLogger.getLogger().reset(); + } + + @Test + void testReconfiguration() throws Exception { + final Configuration cfg = context.getConfiguration(); + assertNotNull(cfg, "No configuration"); + assertInstanceOf(DefaultConfiguration.class, cfg, "Not set to default configuration"); + final URI configLocation = LateConfigTest.class.getResource(CONFIG).toURI(); + final LoggerContext loggerContext = LoggerContext.getContext(null, false, configLocation); + assertNotNull(loggerContext, "No Logger Context"); + assertThat(loggingPath.resolve("test-xml.log")).exists(); + final Configuration newConfig = loggerContext.getConfiguration(); + assertNotSame(cfg, newConfig, "Configuration not reset"); + assertInstanceOf(XmlConfiguration.class, newConfig, "Reconfiguration failed"); + context = LoggerContext.getContext(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..e2c292b7307 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LevelTest.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; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.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.message.ObjectMessage; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-Level.xml") +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 + 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..95418cabfcb --- /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 static org.junit.jupiter.api.Assertions.assertInstanceOf; + +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; + +/** + * Tests logging during shutdown. + */ +@Tag("functional") +class Log4j1222Test { + + @Test + void homepageRendersSuccessfully() { + System.setProperty("log4j.configurationFile", "log4j2-console.xml"); + 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"); + assertInstanceOf( + TestLogger.class, + Holder.LOGGER, + "Logger is of type " + Holder.LOGGER.getClass().getName()); + if (((TestLogger) Holder.LOGGER).getEntries().isEmpty()) { + 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..ce42d5d851d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Field; +import java.util.List; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; +import org.apache.logging.log4j.core.impl.LocationAwareLogEventFactory; +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.LoggerContextRule; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.Message; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runners.model.Statement; + +/** + * + */ +public class LogEventFactoryTest { + + private static final String CONFIG = "log4j2-config.xml"; + private static final LoggerContextRule context = new LoggerContextRule(CONFIG); + + private ListAppender app; + + // this would look so cool using lambdas + @ClassRule + public static RuleChain chain = RuleChain.outerRule((base, description) -> new Statement() { + @Override + public void evaluate() throws Throwable { + System.setProperty(Constants.LOG4J_LOG_EVENT_FACTORY, TestLogEventFactory.class.getName()); + resetLogEventFactory(new TestLogEventFactory()); + try { + base.evaluate(); + } finally { + System.clearProperty(Constants.LOG4J_LOG_EVENT_FACTORY); + resetLogEventFactory(new DefaultLogEventFactory()); + } + } + + private void resetLogEventFactory(final LogEventFactory logEventFactory) throws IllegalAccessException { + final Field field = FieldUtils.getField(LoggerConfig.class, "LOG_EVENT_FACTORY", true); + FieldUtils.removeFinalModifier(field); + FieldUtils.writeStaticField(field, logEventFactory, false); + } + }) + .around(context); + + @Before + public void before() { + app = context.getListAppender("List").clear(); + } + + @Test + public void testEvent() { + final org.apache.logging.log4j.Logger logger = context.getLogger("org.apache.test.LogEventFactory"); + logger.error("error message"); + final List events = app.getEvents(); + assertNotNull("No events", events); + assertEquals("Incorrect number of events. Expected 1, actual " + events.size(), 1, events.size()); + final LogEvent event = events.get(0); + assertEquals("TestLogEventFactory wasn't used", "Test", event.getLoggerName()); + } + + public static class TestLogEventFactory implements LogEventFactory, LocationAwareLogEventFactory { + + @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 new Log4jLogEvent("Test", marker, fqcn, level, data, properties, t); + } + + @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 new Log4jLogEvent("Test", marker, fqcn, level, data, properties, t); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.java new file mode 100644 index 00000000000..10aeeb5b3b0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.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; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.ObjectInputStream; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * + */ +class LogEventTest { + + private static final Message MESSAGE = new SimpleMessage("This is a test"); + private static final TestClass TESTER = new TestClass(); + + @Test + void testSerialization() throws Exception { + final LogEvent event1 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .build(); + final Exception parent = new IllegalStateException("Test"); + final Throwable child = new LoggingException("This is a test", parent); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .setThrown(child) // + .build(); + + final byte[] data = SerialUtil.serialize(event1, event2); + + try (final ObjectInputStream ois = SerialUtil.getObjectInputStream(data)) { + assertDoesNotThrow(ois::readObject, "Failed to deserialize event1"); + assertDoesNotThrow(ois::readObject, "Failed to deserialize event1"); + } + } + + @Test + void testNanoTimeIsNotSerialized1() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .setThreadName("this must be initialized or the test fails") // + .setNanoTime(12345678L) // + .build(); + + final LogEvent expected = new Log4jLogEvent.Builder(event).build(); + final LogEvent actual = SerialUtil.deserialize(SerialUtil.serialize(event)); + + assertNotEquals(expected, actual, "Different event: nanoTime"); + assertNotEquals(expected.getNanoTime(), actual.getNanoTime(), "Different nanoTime"); + assertEquals(0, actual.getNanoTime(), "deserialized nanoTime is zero"); + } + + @Test + void testNanoTimeIsNotSerialized2() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .setThreadId(1) // this must be initialized or the test fails + .setThreadName("this must be initialized or the test fails") // + .setThreadPriority(2) // this must be initialized or the test fails + .setNanoTime(0) // + .build(); + + final LogEvent expected = new Log4jLogEvent.Builder(event).build(); + final LogEvent actual = SerialUtil.deserialize(SerialUtil.serialize(event)); + assertEquals(expected, actual, "both zero nanoTime"); + } + + @Test + @Disabled + void testEquals() { + final LogEvent event1 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .build(); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .build(); + final LogEvent event3 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) // + .build(); + assertNotEquals(event1, event2, "Events should not be equal"); + assertEquals(event2, event3, "Events should be equal"); + } + + @Test + void testLocation() { + final StackTraceElement ste = TESTER.getEventSource(this.getClass().getName()); + assertNotNull(ste, "No StackTraceElement"); + assertEquals(this.getClass().getName(), ste.getClassName(), "Incorrect event"); + } + + private static class TestClass { + private static final String FQCN = TestClass.class.getName(); + + public StackTraceElement getEventSource(final String loggerName) { + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(loggerName) + .setLoggerFqcn(FQCN) + .setLevel(Level.INFO) + .setMessage(MESSAGE) + .build(); + event.setIncludeLocation(true); + return event.getSource(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java new file mode 100644 index 00000000000..e6d58f66c79 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.withSettings; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +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.DefaultConfiguration; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.MessageFactory2; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junitpioneer.jupiter.Issue; + +class LoggerContextTest { + + private static final int LOGGER_COUNT = 1024; + private static final int CONCURRENCY_LEVEL = 16; + + @Test + void newInstance_should_honor_name_and_message_factory(final TestInfo testInfo) { + final String testName = testInfo.getDisplayName(); + try (final LoggerContext loggerContext = new LoggerContext(testName)) { + final String loggerName = testName + "-loggerName"; + final MessageFactory2 messageFactory = mock(MessageFactory2.class); + final Logger logger = loggerContext.newInstance(loggerContext, loggerName, messageFactory); + assertThat(logger.getName()).isEqualTo(loggerName); + assertThat((MessageFactory) logger.getMessageFactory()).isSameAs(messageFactory); + } + } + + @Test + void getLoggers_can_be_updated_concurrently(final TestInfo testInfo) { + final String testName = testInfo.getDisplayName(); + final ExecutorService executorService = Executors.newFixedThreadPool(CONCURRENCY_LEVEL); + try (LoggerContext loggerContext = new LoggerContext(testName)) { + // Create a logger + Collection> tasks = IntStream.range(0, CONCURRENCY_LEVEL) + .mapToObj(i -> executorService.submit(() -> { + // Iterates over loggers + loggerContext.updateLoggers(); + // Create some loggers + for (int j = 0; j < LOGGER_COUNT; j++) { + loggerContext.getLogger(testName + "-" + i + "-" + j); + } + // Iterate over loggers again + loggerContext.updateLoggers(); + })) + .collect(Collectors.toList()); + Assertions.assertDoesNotThrow(() -> { + for (Future task : tasks) { + task.get(); + } + }); + } finally { + executorService.shutdown(); + } + } + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/3770") + void start_should_fallback_on_reconfigure_if_context_already_started(final TestInfo testInfo) { + final String testName = testInfo.getDisplayName(); + try (final LoggerContext loggerContext = new LoggerContext(testName)) { + loggerContext.start(); + assertThat(loggerContext.isStarted()).isTrue(); + assertThat(loggerContext.getConfiguration()).isInstanceOf(DefaultConfiguration.class); + // Start + Configuration configuration = mock( + AbstractConfiguration.class, + withSettings() + .useConstructor(null, ConfigurationSource.NULL_SOURCE) + .defaultAnswer(CALLS_REAL_METHODS)); + loggerContext.start(configuration); + assertThat(loggerContext.getConfiguration()).isSameAs(configuration); + } + } +} 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..e23dcfbea02 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerDateTest.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; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Calendar; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-date.xml") +public class LoggerDateTest { + + private final FileAppender fileApp; + + public LoggerDateTest(@Named("File") final FileAppender fileApp) { + this.fileApp = fileApp; + } + + @Test + 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/LoggerMessageFactoryCustomizationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryCustomizationTest.java new file mode 100644 index 00000000000..760b1afeb8e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryCustomizationTest.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.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.message.AbstractMessageFactory; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +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.ParameterizedMessageFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty( + key = "log4j2.messageFactory", + value = "org.apache.logging.log4j.core.LoggerMessageFactoryCustomizationTest$AlternativeTestMessageFactory") +@SetSystemProperty( + key = "log4j2.flowMessageFactory", + value = "org.apache.logging.log4j.core.LoggerMessageFactoryCustomizationTest$AlternativeTestFlowMessageFactory") +class LoggerMessageFactoryCustomizationTest { + + @Test + void arguments_should_be_honored(TestInfo testInfo) { + try (LoggerContext loggerContext = + new LoggerContext(LoggerMessageFactoryCustomizationTest.class.getSimpleName())) { + Logger logger = new Logger( + loggerContext, testInfo.getDisplayName(), new TestMessageFactory(), new TestFlowMessageFactory()); + assertTestMessageFactories(logger, TestMessageFactory.class, TestFlowMessageFactory.class); + } + } + + @Test + void properties_should_be_honored(TestInfo testInfo) { + try (LoggerContext loggerContext = + new LoggerContext(LoggerMessageFactoryCustomizationTest.class.getSimpleName())) { + Logger logger = loggerContext.getLogger(testInfo.getDisplayName()); + assertTestMessageFactories( + logger, AlternativeTestMessageFactory.class, AlternativeTestFlowMessageFactory.class); + } + } + + private static void assertTestMessageFactories( + Logger logger, + Class messageFactoryClass, + Class flowMessageFactoryClass) { + assertThat(logger.getMessageFactory().getClass()).isEqualTo(messageFactoryClass); + assertThat(logger.getFlowMessageFactory().getClass()).isEqualTo(flowMessageFactoryClass); + } + + public static class TestMessageFactory extends AbstractMessageFactory { + + @Override + public Message newMessage(final String message, final Object... params) { + return ParameterizedMessageFactory.INSTANCE.newMessage(message, params); + } + } + + public static class AlternativeTestMessageFactory extends TestMessageFactory {} + + public static class TestFlowMessageFactory extends DefaultFlowMessageFactory {} + + public static class AlternativeTestFlowMessageFactory extends TestFlowMessageFactory {} +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaDisabledTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaDisabledTest.java new file mode 100644 index 00000000000..28d39c30f74 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaDisabledTest.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; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junitpioneer.jupiter.SetSystemProperty; + +class LoggerMessageFactoryDefaultsTlaDisabledTest { + + @Test + @SetSystemProperty(key = "log4j2.enableThreadLocals", value = "false") + void defaults_should_match_when_thread_locals_disabled(TestInfo testInfo) { + assertThat(Constants.ENABLE_THREADLOCALS).isFalse(); + try (LoggerContext loggerContext = + new LoggerContext(LoggerMessageFactoryDefaultsTlaDisabledTest.class.getSimpleName())) { + final Logger logger = loggerContext.getLogger(testInfo.getDisplayName()); + assertThat((MessageFactory) logger.getMessageFactory()).isSameAs(ParameterizedMessageFactory.INSTANCE); + assertThat(logger.getFlowMessageFactory()).isSameAs(DefaultFlowMessageFactory.INSTANCE); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaEnabledTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaEnabledTest.java new file mode 100644 index 00000000000..3b8f7ba339e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerMessageFactoryDefaultsTlaEnabledTest.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 static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junitpioneer.jupiter.SetSystemProperty; + +class LoggerMessageFactoryDefaultsTlaEnabledTest { + + @Test + @SetSystemProperty(key = "log4j2.isWebapp", value = "false") + @SetSystemProperty(key = "log4j2.enableThreadlocals", value = "true") + void defaults_should_match_when_thread_locals_enabled(TestInfo testInfo) { + assertThat(Constants.ENABLE_THREADLOCALS).isTrue(); + try (LoggerContext loggerContext = + new LoggerContext(LoggerMessageFactoryDefaultsTlaEnabledTest.class.getSimpleName())) { + Logger logger = loggerContext.getLogger(testInfo.getDisplayName()); + assertThat((MessageFactory) logger.getMessageFactory()).isSameAs(ReusableMessageFactory.INSTANCE); + assertThat(logger.getFlowMessageFactory()).isSameAs(DefaultFlowMessageFactory.INSTANCE); + } + } +} 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..1f244a28900 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerSerializationTest.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; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +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 + public static Collection data() { + return Arrays.asList(new Object[][] { + {new LoggerContext("").getLogger("", null)}, + {LogManager.getRootLogger()}, + {LogManager.getLogger()}, + {LogManager.getLogger("test")} + }); + } + + public LoggerSerializationTest(final Serializable serializable) { + super(serializable); + } +} 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..bcd8d7bd26f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java @@ -0,0 +1,563 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.CoreMatchers.equalTo; +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.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.AbstractLogger; +import org.apache.logging.log4j.spi.MessageFactory2Adapter; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +@LoggerContextSource(value = LoggerTest.CONFIG, reconfigure = ReconfigurationPolicy.AFTER_EACH) +class LoggerTest { + + static final String CONFIG = "log4j-test2.xml"; + + private static void checkMessageFactory(final MessageFactory messageFactory1, final Logger testLogger1) { + if (messageFactory1 == null) { + assertEquals( + AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, + testLogger1.getMessageFactory().getClass()); + } else { + MessageFactory actual = testLogger1.getMessageFactory(); + if (actual instanceof MessageFactory2Adapter) { + actual = ((MessageFactory2Adapter) actual).getOriginal(); + } + assertEquals(messageFactory1, 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; + org.apache.logging.log4j.Logger loggerClass; + + 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"); + loggerClass = context.getLogger(LoggerTest.class); + 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 + void basicFlow() { + logger.traceEntry(); + logger.traceExit(); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + void builder() { + logger.atDebug().withLocation().log("Hello"); + final 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:129)", + 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 + void catching() { + try { + throw new NullPointerException(); + } catch (final Exception e) { + logger.catching(e); + } + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + void debug() { + logger.debug("Debug message"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + void debugChangeLevel_ForClass() { + loggerClass.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(LoggerTest.class, Level.OFF); + loggerClass.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(LoggerTest.class, Level.DEBUG); + loggerClass.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + void debugObject() { + logger.debug(new Date()); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + void debugWithParms() { + logger.debug("Hello, {}", "World"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + 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 + 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 + 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 + void paramWithExceptionTest() { + 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 + void simpleFlow() { + logger.entry(CONFIG); + logger.traceExit(0); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + void simpleFlowDepreacted() { + logger.entry(CONFIG); + logger.exit(0); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + 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 + void testAdditivity(final LoggerContext context) { + final Logger localLogger = context.getLogger("org.apache.test"); + localLogger.error("Test parent additivity"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + 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 + void testLevelInheritance(final LoggerContext context) { + 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( + Level.DEBUG, + localLogger.getLevel(), + "Incorrect level - expected DEBUG, actual " + localLogger.getLevel()); + } + + @Test + 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 + 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 + 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..d84bd9f0b51 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerUpdateTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.List; +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; + +@LoggerContextSource("log4j-test2.xml") +class LoggerUpdateTest { + + private final ListAppender app; + + public LoggerUpdateTest(@Named("List") final ListAppender app) { + this.app = app.clear(); + } + + @Test + 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 LoggerContext ctx = LoggerContext.getContext(false); + final Configuration config = ctx.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); + ctx.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 + void testUpdateLoggersPropertyListeners(final LoggerContext context) { + 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..ce41afd2ed8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LookupTest.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; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-lookup.xml") +class LookupTest { + + @Test + void testHostname(@Named final ConsoleAppender console) { + final Layout layout = console.getLayout(); + assertNotNull(layout, "No Layout"); + assertInstanceOf(PatternLayout.class, layout, "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/MonitorResourcesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorResourcesTest.java new file mode 100644 index 00000000000..be395a2d86d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorResourcesTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +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.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.properties.PropertiesConfiguration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Source; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +class MonitorResourcesTest { + + @Test + void test_reconfiguration(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws IOException { + final ConfigurationBuilder configBuilder = ConfigurationBuilderFactory.newConfigurationBuilder( + // Explicitly deviating from `BuiltConfiguration`, since it is not `Reconfigurable`, and this + // results in `instanceof Reconfigurable` checks in `AbstractConfiguration` to fail while arming + // the watcher. Hence, using `PropertiesConfiguration`, which is `Reconfigurable`, instead. + PropertiesConfiguration.class); + final Path configFile = tempDir.resolve("log4j.xml"); + final Path externalResourceFile1 = tempDir.resolve("external-resource-1.txt"); + final Path externalResourceFile2 = tempDir.resolve("external-resource-2.txt"); + final ConfigurationSource configSource = new ConfigurationSource(new Source(configFile), new byte[] {}, 0); + final int monitorInterval = 3; + + final ComponentBuilder monitorResourcesComponent = configBuilder.newComponent("MonitorResources"); + monitorResourcesComponent.addComponent(configBuilder + .newComponent("MonitorResource") + .addAttribute("uri", externalResourceFile1.toUri().toString())); + monitorResourcesComponent.addComponent(configBuilder + .newComponent("MonitorResource") + .addAttribute("uri", externalResourceFile2.toUri().toString())); + + final Configuration config = configBuilder + .setConfigurationSource(configSource) + .setMonitorInterval(String.valueOf(monitorInterval)) + .addComponent(monitorResourcesComponent) + .build(false); + + try (final LoggerContext loggerContext = Configurator.initialize(config)) { + assertMonitorResourceFileNames( + loggerContext, + configFile.getFileName().toString(), + externalResourceFile1.getFileName().toString(), + externalResourceFile2.getFileName().toString()); + Files.write(externalResourceFile2, Collections.singletonList("a change")); + waitAtMost(2 * monitorInterval, TimeUnit.SECONDS).until(() -> loggerContext.getConfiguration() != config); + } + } + + @Test + @LoggerContextSource("config/MonitorResource/log4j.xml") + void test_config_of_type_XML(final LoggerContext loggerContext) { + assertMonitorResourceFileNames(loggerContext, "log4j.xml"); + } + + @Test + @LoggerContextSource("config/MonitorResource/log4j.json") + void test_config_of_type_JSON(final LoggerContext loggerContext) { + assertMonitorResourceFileNames(loggerContext, "log4j.json"); + } + + @Test + @LoggerContextSource("config/MonitorResource/log4j.yaml") + void test_config_of_type_YAML(final LoggerContext loggerContext) { + assertMonitorResourceFileNames(loggerContext, "log4j.yaml"); + } + + @Test + @LoggerContextSource("config/MonitorResource/log4j.properties") + void test_config_of_type_properties(final LoggerContext loggerContext) { + assertMonitorResourceFileNames(loggerContext, "log4j.properties"); + } + + private static void assertMonitorResourceFileNames(final LoggerContext loggerContext, final String configFileName) { + assertMonitorResourceFileNames(loggerContext, configFileName, "external-file-1.txt", "external-file-2.txt"); + } + + private static void assertMonitorResourceFileNames( + final LoggerContext loggerContext, final String configFileName, final String... externalResourceFileNames) { + final Set sources = loggerContext + .getConfiguration() + .getWatchManager() + .getConfigurationWatchers() + .keySet(); + final Set actualFileNames = + sources.stream().map(source -> source.getFile().getName()).collect(Collectors.toSet()); + final Set expectedFileNames = new LinkedHashSet<>(); + expectedFileNames.add(configFileName); + expectedFileNames.addAll(Arrays.asList(externalResourceFileNames)); + assertThat(actualFileNames).as("watch manager sources: %s", sources).isEqualTo(expectedFileNames); + } +} 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..20eef664356 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.ThreadContext; +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.ClassRule; +import org.junit.Test; + +public class PatternResolverDoesNotEvaluateThreadContextTest { + + private static final String CONFIG = "log4j2-pattern-layout-with-context.xml"; + private static final String PARAMETER = "user"; + private ListAppender listAppender; + + @ClassRule + public static LoggerContextRule context = new LoggerContextRule(CONFIG); + + @Before + public void before() { + listAppender = context.getRequiredAppender("list", ListAppender.class); + listAppender.clear(); + } + + @Test + public void testNoUserSet() { + final Logger logger = context.getLogger(getClass()); + logger.info("This is a test"); + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages returned"); + final String message = messages.get(0); + assertEquals( + "INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user} This is a test", + message); + } + + @Test + public void testMessageIsNotLookedUp() { + final Logger logger = context.getLogger(getClass()); + logger.info("This is a ${upper:test}"); + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages returned"); + final 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() { + final Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "123"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages returned"); + final String message = messages.get(0); + assertEquals( + "INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest 123 This is a test", + message); + } + + @Test + public void testUserIsLookup() { + final Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "${java:version}"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages returned"); + final String message = messages.get(0); + assertEquals( + "INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest ${java:version} This is a test", + message); + } + + @Test + public void testUserHasLookup() { + final Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "user${java:version}name"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + final List messages = listAppender.getMessages(); + assertTrue(messages != null && !messages.isEmpty(), "No messages returned"); + final 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/PatternSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternSelectorTest.java new file mode 100644 index 00000000000..827c39f97b7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternSelectorTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.LogManager; +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.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "bsh, Javascript") +@LoggerContextSource("log4j-patternSelector.xml") +class PatternSelectorTest { + + private static final int CURRENT_LINE = 36; + + @Test + void testMarkerPatternSelector(@Named("List") final ListAppender app) { + final org.apache.logging.log4j.Logger logger = LogManager.getLogger("TestMarkerPatternSelector"); + logger.traceEntry(); + logger.info("Hello World"); + logger.traceExit(); + final List messages = app.getMessages(); + assertNotNull(messages, "No Messages"); + assertEquals( + 3, + messages.size(), + "Incorrect number of messages. Expected 3, Actual " + messages.size() + ": " + messages); + final String expect = String.format( + "[TRACE] TestMarkerPatternSelector ====== " + + "o.a.l.l.c.PatternSelectorTest.testMarkerPatternSelector:%d Enter ======%n", + CURRENT_LINE + 5); + assertEquals(expect, messages.get(0)); + assertEquals("[INFO ] TestMarkerPatternSelector Hello World" + Strings.LINE_SEPARATOR, messages.get(1)); + app.clear(); + } + + @Test + void testScriptPatternSelector(@Named("List2") final ListAppender app) { + final org.apache.logging.log4j.Logger logger = LogManager.getLogger("TestScriptPatternSelector"); + final org.apache.logging.log4j.Logger logger2 = LogManager.getLogger("NoLocation"); + logger.traceEntry(); + logger.info("Hello World"); + logger2.info("No location information"); + logger.traceExit(); + final List messages = app.getMessages(); + assertNotNull(messages, "No Messages"); + assertEquals( + 4, + messages.size(), + "Incorrect number of messages. Expected 4, Actual " + messages.size() + ": " + messages); + String expect = String.format( + "[TRACE] TestScriptPatternSelector ====== " + + "o.a.l.l.c.PatternSelectorTest.testScriptPatternSelector:%d Enter ======%n", + CURRENT_LINE + 27); + assertEquals(expect, messages.get(0)); + expect = String.format( + "[INFO ] TestScriptPatternSelector o.a.l.l.c.PatternSelectorTest.testScriptPatternSelector.%d " + + "Hello World%n", + CURRENT_LINE + 28); + assertEquals(expect, messages.get(1)); + assertEquals("[INFO ] NoLocation No location information" + Strings.LINE_SEPARATOR, messages.get(2)); + app.clear(); + } + + @Test + void testJavaScriptPatternSelector(@Named("List3") final ListAppender app) { + final org.apache.logging.log4j.Logger logger = LogManager.getLogger("TestJavaScriptPatternSelector"); + final org.apache.logging.log4j.Logger logger2 = LogManager.getLogger("JavascriptNoLocation"); + logger.traceEntry(); + logger.info("Hello World"); + logger2.info("No location information"); + logger.traceExit(); + final List messages = app.getMessages(); + assertNotNull(messages, "No Messages"); + assertEquals( + 4, + messages.size(), + "Incorrect number of messages. Expected 4, Actual " + messages.size() + ": " + messages); + String expect = String.format( + "[TRACE] TestJavaScriptPatternSelector ====== " + + "o.a.l.l.c.PatternSelectorTest.testJavaScriptPatternSelector:%d Enter ======%n", + CURRENT_LINE + 55); + assertEquals(expect, messages.get(0)); + expect = String.format( + "[INFO ] TestJavaScriptPatternSelector " + + "o.a.l.l.c.PatternSelectorTest.testJavaScriptPatternSelector.%d Hello World%n", + CURRENT_LINE + 56); + assertEquals(expect, messages.get(1)); + assertEquals("[INFO ] JavascriptNoLocation No location information" + Strings.LINE_SEPARATOR, messages.get(2)); + app.clear(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternVariableResolverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternVariableResolverTest.java new file mode 100644 index 00000000000..4b4c0739c7e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternVariableResolverTest.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; + +import static org.junit.Assert.assertTrue; + +import java.util.List; +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.ClassRule; +import org.junit.Test; + +/** + * Class Description goes here. + */ +public class PatternVariableResolverTest { + + private static final String CONFIG = "log4j2-pattern-layout.xml"; + private ListAppender listAppender; + + @ClassRule + public static LoggerContextRule context = new LoggerContextRule(CONFIG); + + @Before + public void before() { + listAppender = context.getRequiredAppender("list", ListAppender.class); + } + + @Test + public void testFileName() { + final Logger logger = context.getLogger(PatternVariableResolverTest.class); + logger.info("This is a test"); + final List messages = listAppender.getMessages(); + assertTrue("No messages returned", messages != null && !messages.isEmpty()); + final String message = messages.get(0); + System.out.println(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..4a4bf8dba0a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.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; + +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.concurrent.TimeUnit; +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.Test; + +@LoggerContextSource("log4j-test2.properties") +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 + 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..559a5d6e3e6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ReusableParameterizedMessageMemoryLeakTest.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; + +import static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; + +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.junit.jupiter.api.Test; + +class ReusableParameterizedMessageMemoryLeakTest { + + @Test + void parameters_should_be_garbage_collected() throws Exception { + awaitGarbageCollection(() -> { + final ParameterObject parameter = new ParameterObject("paramValue"); + final ReusableMessage message = + (ReusableMessage) ReusableMessageFactory.INSTANCE.newMessage("foo {}", parameter); + // Large enough for the parameters, but smaller than the default reusable array size + message.swapParameters(new Object[5]); + return parameter; + }); + } + + private static final class ParameterObject { + + private final String value; + + private ParameterObject(final String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } +} 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..4bfa9f5d6ad --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.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; + +import static org.apache.logging.log4j.core.util.ReflectionUtil.getFieldValue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.lang.reflect.Field; +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.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +@SetTestProperty(key = "log4j2.isWebapp", value = "false") +class ShutdownDisabledTest { + + private static final Field shutdownCallbackField; + + static { + try { + shutdownCallbackField = LoggerContext.class.getDeclaredField("shutdownCallback"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + @Test + @LoggerContextSource("log4j-test3.xml") + void testShutdownFlag(final Configuration config, final LoggerContext ctx) { + assertThat(config.isShutdownHookEnabled()) + .as("Shutdown hook is enabled") + .isFalse(); + assertThat(getFieldValue(shutdownCallbackField, ctx)) + .as("Shutdown callback") + .isNull(); + } + + @Test + void whenLoggerContextInitialized_respectsShutdownDisabled(TestInfo testInfo) { + Configuration configuration = mockConfiguration(); + when(configuration.isShutdownHookEnabled()).thenReturn(false); + try (final LoggerContext ctx = new LoggerContext(testInfo.getDisplayName())) { + ctx.start(configuration); + assertThat(ctx.isStarted()).isTrue(); + assertThat(ctx.getConfiguration()).isSameAs(configuration); + assertThat(getFieldValue(shutdownCallbackField, ctx)) + .as("Shutdown callback") + .isNull(); + } + } + + @Test + void whenLoggerContextStarted_ignoresShutdownDisabled(TestInfo testInfo) { + // Traditional behavior: during reconfiguration, the shutdown hook is not removed. + Configuration initialConfiguration = mockConfiguration(); + when(initialConfiguration.isShutdownHookEnabled()).thenReturn(true); + Configuration configuration = mockConfiguration(); + when(configuration.isShutdownHookEnabled()).thenReturn(false); + try (final LoggerContext ctx = new LoggerContext(testInfo.getDisplayName())) { + ctx.start(initialConfiguration); + assertThat(ctx.isStarted()).isTrue(); + Object shutdownCallback = getFieldValue(shutdownCallbackField, ctx); + assertThat(shutdownCallback).as("Shutdown callback").isNotNull(); + ctx.start(configuration); + assertThat(getFieldValue(shutdownCallbackField, ctx)) + .as("Shutdown callback") + .isSameAs(shutdownCallback); + } + } + + private static Configuration mockConfiguration() { + return mock( + AbstractConfiguration.class, + withSettings() + .useConstructor(null, ConfigurationSource.NULL_SOURCE) + .defaultAnswer(CALLS_REAL_METHODS)); + } +} 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..66855a51c74 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownTimeoutConfigurationTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-test-shutdownTimeout.xml") +class ShutdownTimeoutConfigurationTest { + + @Test + 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..0bd00104b17 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.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.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Date; +import java.util.List; +import java.util.Locale; +import org.apache.logging.log4j.MarkerManager; +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.message.EntryMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-strict1.xml") +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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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..0608c9dd150 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.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 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.util.StringBuilders; + +/** + * {@link TestPatternConverters} provides {@link LogEventPatternConverter} implementations that may be + * useful in tests. + */ +public final class TestPatternConverters { + private TestPatternConverters() {} + + @Plugin(name = "TestParametersPatternConverter", category = "Converter") + @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('['); + final 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(']'); + } + } + + @Plugin(name = "TestFormatPatternConverter", category = "Converter") + @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/TimestampMessageTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java new file mode 100644 index 00000000000..d06e1d63d08 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +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.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.Constants; +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.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Confirms that if you log a {@link TimestampMessage} then there are no unnecessary calls to {@link Clock}. + *

+ * See LOG4J2-744. + *

+ */ +@LoggerContextSource("log4j2-744.xml") +class TimestampMessageTest { + private ListAppender app; + + public TimestampMessageTest(@Named("List") final ListAppender app) { + this.app = app.clear(); + } + + @BeforeAll + static void beforeClass() { + System.setProperty(ClockFactory.PROPERTY_NAME, PoisonClock.class.getName()); + } + + @AfterAll + static void afterClass() throws IllegalAccessException { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + ClockFactoryTest.resetClocks(); + } + + @Test + void testTimestampMessage(final LoggerContext context) { + final Logger log = context.getLogger("TimestampMessageTest"); + log.info((Message) new TimeMsg("Message with embedded timestamp", 123456789000L)); + final List msgs = app.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() { + // Breakpoint here for debuging. + } + + @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-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.java new file mode 100644 index 00000000000..15e8856c473 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.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.Locale; +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.ThreadContext; +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.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("xml-events.xml") +@Disabled("TODO") +class XmlEvents { + + @Test + void testEvents() { + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + final TransferMessage msg = new TransferMessage(); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + EventLogger.logEvent(msg); + msg.setCompletionStatus("Transfer Complete"); + EventLogger.logEvent(msg); + ThreadContext.clearMap(); + // TODO: do something with the results + + } + + @AsynchronouslyFormattable + private static class TransferMessage extends StructuredDataMessage { + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = -4334703653495359785L; + + public TransferMessage() { + super("Transfer@18060", null, "Audit"); + } + + public void setCompletionStatus(final String msg) { + setMessageFormat(msg); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AbstractAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AbstractAppenderBuilderTest.java new file mode 100644 index 00000000000..6035ea1425d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AbstractAppenderBuilderTest.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.appender; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.junit.jupiter.api.Test; + +class AbstractAppenderBuilderTest { + + @Test + void testDefaultLayoutLeak() { + final int expected = AbstractManager.getManagerCount(); + final Configuration configuration = new DefaultConfiguration(); + final ConsoleAppender appender = ConsoleAppender.newBuilder() + .setConfiguration(configuration) + .setName("test") + .build(); + configuration.addAppender(appender); + configuration.start(); + configuration.stop(); + assertEquals(expected, AbstractManager.getManagerCount(), "No managers should be left after shutdown."); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..f0a84f1f4dc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertEquals; + +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.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.Test; + +/** + * Tests the AsyncAppender (LOG4J2-1080) event routing logic: + *
+ * If not BLOCKING, then offer the event to the queue and send to error appender if queue full.
+ * If BLOCKING, then (LOG4J2-471)
+ *     if queue full (non-blocking call to queue.offer(event) failed) then
+ *         if thread==backgroundThread delegate to event router
+ *         else queue.add(event) // blocking call
+ * 
+ */ +@LoggerContextSource("log4j-asynch-queue-full.xml") +class AsyncAppenderQueueFullPolicyTest { + + 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(); + field.set(asyncAppender, policy); + policy.queueFull.set(0L); + } + + @AfterEach + void after() { + blockingAppender.running = false; + policy.queueFull.set(0L); + } + + @Test + void testRouter() { + final Logger logger = LogManager.getLogger(AsyncAppenderQueueFullPolicyTest.class); + + assertEquals(4, asyncAppender.getQueueCapacity()); + logger.error("event 1 - gets taken off the queue"); + logger.warn("event 2"); + logger.info("event 3"); + logger.info("event 4"); + while (asyncAppender.getQueueRemainingCapacity() == 0) { + Thread.yield(); // wait until background thread takes one element off the queue + } + logger.info("event 5 - now the queue is full"); + assertEquals(0, asyncAppender.getQueueRemainingCapacity(), "queue remaining capacity"); + assertEquals(0, policy.queueFull.get(), "EventRouter invocations"); + + final Thread release = new Thread("AsyncAppenderReleaser") { + @Override + public void run() { + while (policy.queueFull.get() == 0) { + try { + Thread.sleep(10L); + } catch (final InterruptedException ignored) { + // ignored + } + } + blockingAppender.running = false; + } + }; + release.setDaemon(true); + release.start(); + logger.fatal("this blocks until queue space available"); + assertEquals(1, policy.queueFull.get()); + } + + public static class CountingAsyncQueueFullPolicy extends DefaultAsyncQueueFullPolicy { + AtomicLong queueFull = new AtomicLong(); + + @Override + public EventRoute getRoute(final long backgroundThreadId, final Level level) { + queueFull.incrementAndGet(); + return EventRoute.ENQUEUE; + } + } +} 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..815c475071f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +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.Appender; +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.spi.ExtendedLogger; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +class AsyncAppenderTest { + + static void exceptionTest(final LoggerContext context) throws InterruptedException { + assertNotNull(context); + 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 { + assertNotNull(context); + 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") + void testArrayBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + void testDefaultAsyncAppenderConfig(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + + final 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") + void testDisruptorBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + void testGetAppenderRefStrings(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + assertArrayEquals(new String[] {"List"}, appender.getAppenderRefStrings()); + assertNotSame(appender.getAppenderRefStrings(), appender.getAppenderRefStrings()); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + void testGetAppenders(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + final List appenders = appender.getAppenders(); + assertEquals(1, appenders.size()); + final Appender listAppender = appenders.get(0); + assertEquals("List", listAppender.getName()); + assertInstanceOf(ListAppender.class, listAppender); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + void testGetErrorRef(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + assertEquals("STDOUT", appender.getErrorRef()); + } + + @Test + @Tag("jctools") + @LoggerContextSource("BlockingQueueFactory-JCToolsBlockingQueue.xml") + void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("BlockingQueueFactory-LinkedTransferQueue.xml") + void testLinkedTransferQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch-no-location.xml") + 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") + void testShutdownTimeout(final LoggerContext context) { + context.getLogger("Logger").info("This is a test"); + context.stop(); + } + + @Test + @LoggerContextSource("log4j-asynch-location.xml") + public void testRequiresLocation(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + assertTrue(appender.requiresLocation()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.java new file mode 100644 index 00000000000..96c1635a18e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/BufferingErrorHandler.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.appender; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.ErrorHandler; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * {@link ErrorHandler} implementation buffering invocations in the form of {@link StatusData}. + */ +final class BufferingErrorHandler implements ErrorHandler { + + private final List statusDataBuffer = Collections.synchronizedList(new ArrayList<>()); + + BufferingErrorHandler() {} + + private void addStatusData(final String message, final Throwable throwable) { + final StackTraceElement caller = StackLocatorUtil.getStackTraceElement(3); + final String threadName = Thread.currentThread().getName(); + final StatusData statusData = + new StatusData(caller, Level.ERROR, new SimpleMessage(message), throwable, threadName); + statusDataBuffer.add(statusData); + } + + @Override + public void error(String message) { + addStatusData(message, null); + } + + @Override + public void error(final String message, final Throwable throwable) { + addStatusData(message, throwable); + } + + @Override + public void error(final String message, final LogEvent event, final Throwable throwable) { + addStatusData(message, throwable); + } + + public void clear() { + statusDataBuffer.clear(); + } + + public List getBuffer() { + return statusDataBuffer; + } +} 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 78% 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..f0aa79bbc2d 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,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.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-test/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 new file mode 100644 index 00000000000..a44d064ab49 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.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; + +import java.io.IOException; +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.Configurator; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest + * of the log entry (time stamp for example) is in the default color for that console. + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiMessagesMain log4j-core/target/test-classes/log4j2-console.xml
+ * 
+ */ +public class ConsoleAppenderAnsiMessagesMain { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiMessagesMain.class); + + public static void main(final String[] args) { + try (final LoggerContext ignored = Configurator.initialize( + ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console.xml")) { + LOG.fatal("\u001b[1;35mFatal message.\u001b[0m"); + LOG.error("\u001b[1;31mError message.\u001b[0m"); + LOG.warn("\u001b[0;33mWarning message.\u001b[0m"); + LOG.info("\u001b[0;32mInformation message.\u001b[0m"); + LOG.debug("\u001b[0;36mDebug message.\u001b[0m"); + LOG.trace("\u001b[0;30mTrace message.\u001b[0m"); + LOG.error("\u001b[1;31mError message.\u001b[0m", new IOException("test")); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..bd8491c6667 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.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.appender; + +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.config.Configurator; + +/** + * Tests LOG4J2-180 + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira180Main log4j-core/target/test-classes/log4j2-180.xml
+ * 
+ */ +public class ConsoleAppenderAnsiStyleJira180Main { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira180Main.class); + + public static void main(final String[] args) { + final String config = args.length == 0 ? "target/test-classes/log4j2-180.xml" : args[0]; + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { + LOG.fatal("Fatal message."); + LOG.error("Error message."); + LOG.warn("Warning message."); + LOG.info("Information message."); + LOG.debug("Debug message."); + LOG.trace("Trace message."); + try { + throw new NullPointerException(); + } catch (final Exception e) { + LOG.error("Error message.", e); + LOG.catching(Level.ERROR, e); + } + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..65a4ed1ee79 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.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.appender; + +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.config.Configurator; + +/** + * Tests LOG4J2-272 + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira272Main log4j-core/target/test-classes/log4j2-272.xml
+ * 
+ */ +public class ConsoleAppenderAnsiStyleJira272Main { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira272Main.class); + + public static void main(final String[] args) { + final String config = args.length == 0 ? "target/test-classes/log4j2-272.xml" : args[0]; + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { + LOG.fatal("Fatal message."); + LOG.error("Error message."); + LOG.warn("Warning message."); + LOG.info("Information message."); + LOG.debug("Debug message."); + LOG.trace("Trace message."); + try { + throw new NullPointerException(); + } catch (final Exception e) { + LOG.error("Error message.", e); + LOG.catching(Level.ERROR, e); + } + LOG.warn("this is ok \n And all \n this have only\t\tblack colour \n and here is colour again?"); + LOG.info("Information message."); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..957a58e9bf6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.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; + +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.config.Configurator; + +/** + * Tests LOG4J2-319 + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleJira319Main log4j-core/target/test-classes/log4j2-319.xml
+ * 
+ */ +public class ConsoleAppenderAnsiStyleJira319Main { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleJira319Main.class); + + public static void main(final String[] args) { + final String config = args.length == 0 ? "target/test-classes/log4j2-319.xml" : args[0]; + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { + LOG.fatal("Fatal message."); + LOG.error("Error message."); + LOG.warn("Warning message."); + LOG.info("Information message."); + LOG.debug("Debug message."); + LOG.trace("Trace message."); + try { + throw new NullPointerException(); + } catch (final Exception e) { + LOG.error("Error message.", e); + LOG.catching(Level.ERROR, e); + } + LOG.warn("this is ok \n And all \n this have only\t\tblack colour \n and here is colour again?"); + LOG.info("Information message."); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..83a6e8632cc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.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; + +import java.io.IOException; +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.Configurator; +import org.junit.jupiter.api.Test; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest + * of the log entry (time stamp for example) is in the default color for that console. + *

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

+ * + *
+ * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleLayoutMain test
+ * 
+ * or: + *
+ * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml
+ * 
+ * + */ +class ConsoleAppenderAnsiStyleLayoutMain { + + public static void main(final String[] args) { + new ConsoleAppenderAnsiStyleLayoutMain().test(args); + } + + /** + * This is a @Test method to make it easy to run from a command line with {@code mvn -Dtest=FQCN test} + */ + @Test + void test() { + test(null); + } + + public void test(final String[] args) { + final String config = + args == null || args.length == 0 ? "target/test-classes/log4j2-console-style-ansi.xml" : args[0]; + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config)) { + final Logger logger = LogManager.getLogger(ConsoleAppenderAnsiStyleLayoutMain.class); + logger.fatal("Fatal message."); + logger.error("Error message."); + logger.warn("Warning message."); + logger.info("Information message."); + logger.debug("Debug message."); + logger.trace("Trace message."); + logger.error("Error message.", new IOException("test")); + } + } +} 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 76% 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 index 09f111b728e..5cdc81ee452 100644 --- 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender; import java.io.IOException; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -32,8 +31,8 @@ public class ConsoleAppenderAnsiStyleNameLayoutMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderAnsiStyleNameLayoutMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), + try (final LoggerContext ignored = Configurator.initialize( + ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-style-name-ansi.xml")) { LOG.fatal("Fatal message."); LOG.error("Error message."); @@ -44,5 +43,4 @@ public static void main(final String[] args) { LOG.error("Error message.", new IOException("test")); } } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.java new file mode 100644 index 00000000000..88abec63f87 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiXExceptionMain.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.appender; + +import java.nio.file.Files; +import java.nio.file.Paths; +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.Configurator; +import org.junit.jupiter.api.Test; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest + * of the log entry (time stamp for example) is in the default color for that console. + *

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

+ * + *
+ * mvn -Dtest=org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiXExceptionMain test
+ * 
+ * + * or, on Windows: + * + *
+ * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiXExceptionMain log4j-core/src/test/resources/log4j2-console-xex-ansi.xml
+ * 
+ * + */ +class ConsoleAppenderAnsiXExceptionMain { + + public static void main(final String[] args) { + new ConsoleAppenderAnsiXExceptionMain().test(args); + } + + /** + * This is a @Test method to make it easy to run from a command line with {@code mvn -Dtest=FQCN test} + */ + @Test + void test() { + test(null); + } + + public void test(final String[] args) { + final String config = + args == null || args.length == 0 ? "target/test-classes/log4j2-console-xex-ansi.xml" : args[0]; + final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), config); + final Logger logger = LogManager.getLogger(ConsoleAppenderAnsiXExceptionMain.class); + try { + Files.getFileStore(Paths.get("?BOGUS?")); + } catch (final Exception e) { + final IllegalArgumentException logE = new IllegalArgumentException("Bad argument foo", e); + logger.error("Gotcha!", logE); + } finally { + Configurator.shutdown(ctx); + } + } +} 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..10ab22436f3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.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.appender; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +class ConsoleAppenderBuilderTest { + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1620 + */ + @Test + 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 + 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 + void testSetNullErrorHandlerIsNotAllowed() { + final ConsoleAppender appender = + ConsoleAppender.newBuilder().setName("test").build(); + final 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-test/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 new file mode 100644 index 00000000000..2432df32c93 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.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; + +import java.io.IOException; +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.Configurator; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed + * to the console in color, but the rest of the log entry (time stamp for + * example) is in the default color for that console. + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml
+ * 
+ */ +public class ConsoleAppenderDefaultSuppressedThrowable { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderDefaultSuppressedThrowable.class); + + public static void main(final String[] args) { + final String config = + args.length == 0 ? "target/test-classes/log4j2-console-default-suppressed-throwable.xml" : args[0]; + test(config); + } + + static void test(final String config) { + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderDefaultSuppressedThrowable.class.getName(), config)) { + final IOException ioEx = new IOException("test suppressed"); + ioEx.addSuppressed(new IOException("test suppressed 1", new IOException("test 1"))); + final IOException ioEx2 = new IOException("test 2"); + ioEx2.addSuppressed(new IOException("test 3")); + ioEx.addSuppressed(new IOException("test suppressed 2", ioEx2)); + final IOException e = new IOException("test", ioEx); + LOG.error("Error message {}, suppressed?", "Hi", e); + } + } +} 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 76% 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 index 16afa9b9703..d9b5eeba270 100644 --- 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender; import java.io.IOException; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -32,8 +31,8 @@ public class ConsoleAppenderHighlightLayoutDefaultMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderHighlightLayoutDefaultMain.class); public static void main(final String[] args) { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - try (final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), + try (final LoggerContext ignored = Configurator.initialize( + ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-highlight-default.xml")) { LOG.fatal("Fatal message."); LOG.error("Error message."); @@ -44,5 +43,4 @@ public static void main(final String[] args) { LOG.error("Error message.", new IOException("test")); } } - } 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 79% 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 index 89d0f44e902..06655ced400 100644 --- 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender; import java.io.IOException; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -32,7 +31,8 @@ public class ConsoleAppenderHighlightLayoutLogbackMain { private static final Logger LOG = LogManager.getLogger(ConsoleAppenderHighlightLayoutLogbackMain.class); public static void main(final String[] args) { - try (final LoggerContext ctx = Configurator.initialize(ConsoleAppenderAnsiMessagesMain.class.getName(), + try (final LoggerContext ctx = Configurator.initialize( + ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-highlight-logback.xml")) { LOG.fatal("Fatal message."); LOG.error("Error message."); @@ -43,5 +43,4 @@ public static void main(final String[] args) { LOG.error("Error message.", new IOException("test")); } } - } diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ad86c245707 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.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.appender; + +import java.io.IOException; +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.Configurator; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest + * of the log entry (time stamp for example) is in the default color for that console. + */ +public class ConsoleAppenderHighlightLayoutMain { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderHighlightLayoutMain.class); + + public static void main(final String[] args) { + try (final LoggerContext ignored = Configurator.initialize( + ConsoleAppenderAnsiMessagesMain.class.getName(), "target/test-classes/log4j2-console-highlight.xml")) { + LOG.fatal("Fatal message."); + LOG.error("Error message."); + LOG.warn("Warning message."); + LOG.info("Information message."); + LOG.debug("Debug message."); + LOG.trace("Trace message."); + LOG.error("Error message.", new IOException("test")); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..b1177acdd25 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.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.appender; + +/** + * Tests LOG4J2-1002. + */ +public class ConsoleAppenderJira1002ShortThrowableLayoutMain { + + public static void main() { + ConsoleAppenderNoAnsiStyleLayoutMain.test("target/test-classes/log4j2-1002.xml"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..1165fc6a609 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.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.appender; + +import java.io.IOException; +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.Configurator; + +/** + * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest + * of the log entry (time stamp for example) is in the default color for that console. + *

+ * 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 org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml
+ * 
+ */ +public class ConsoleAppenderNoAnsiStyleLayoutMain { + + private static final Logger LOG = LogManager.getLogger(ConsoleAppenderNoAnsiStyleLayoutMain.class); + + private static void logThrowableFromMethod() { + LOG.error("Error message.", new IOException("test")); + } + + public static void main(final String[] args) { + final String config = args.length == 0 ? "target/test-classes/log4j2-console-style-no-ansi.xml" : args[0]; + test(config); + } + + static void test(final String config) { + try (final LoggerContext ignored = + Configurator.initialize(ConsoleAppenderNoAnsiStyleLayoutMain.class.getName(), config)) { + LOG.fatal("Fatal message."); + LOG.error("Error message."); + LOG.warn("Warning message."); + LOG.info("Information message."); + LOG.debug("Debug message."); + LOG.trace("Trace message."); + logThrowableFromMethod(); + // This will log the stack trace as well: + final IOException ioException = new IOException("test"); + LOG.error("Error message {}", "Hi", ioException); + final Throwable t = new IOException("test suppressed"); + t.addSuppressed(new IOException("test suppressed 2", ioException)); + LOG.error("Error message {}, suppressed?", "Hi", t); + LOG.error("Error message {}, suppressed?", "Hi", new IOException("test", t)); + } + } +} 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..e5ddab6eafd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.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 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.atLeastOnce; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ConsoleAppenderTest { + + private static final String LOG4J_SKIP_JANSI = "log4j.skipJansi"; + + @AfterAll + static void afterClass() { + System.clearProperty(LOG4J_SKIP_JANSI); + } + + @BeforeAll + static void beforeClass() { + System.setProperty(LOG4J_SKIP_JANSI, "true"); + } + + ByteArrayOutputStream baos; + + @Mock + PrintStream psMock; + + @BeforeEach + void before() { + System.setProperty(LOG4J_SKIP_JANSI, "true"); + 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 + void testFollowSystemErr() { + testFollowSystemPrintStream(System.err, Target.SYSTEM_ERR, SystemSetter.SYSTEM_ERR); + } + + @Test + 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 + void testSystemErrStreamManagerDoesNotClose() { + testConsoleStreamManagerDoesNotClose(System.err, Target.SYSTEM_ERR, SystemSetter.SYSTEM_ERR); + } + + @Test + void testSystemOutStreamManagerDoesNotClose() { + testConsoleStreamManagerDoesNotClose(System.out, Target.SYSTEM_OUT, SystemSetter.SYSTEM_OUT); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java index 2443d5b0209..8502131808d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/CsvJsonParameterLayoutFileAppenderTest.java @@ -1,39 +1,36 @@ /* * 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.logging.log4j.core.appender; +import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; - -import org.apache.logging.log4j.categories.Layouts; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; -import com.google.common.io.Files; - /** * Tests https://issues.apache.org/jira/browse/LOG4J2-1502 */ @@ -47,7 +44,7 @@ public class CsvJsonParameterLayoutFileAppenderTest { @Rule public RuleChain rule = loggerContextRule.withCleanFilesRule(FILE_PATH); - public void testNoNulCharacters(final String message, final String expected) throws IOException { + private void testNoNulCharacters(final String message, final String expected) throws IOException { @SuppressWarnings("resource") final LoggerContext loggerContext = loggerContextRule.getLoggerContext(); final Logger logger = loggerContext.getLogger("com.example"); @@ -105,13 +102,14 @@ public void testNoNulCharactersOpenSquare() throws IOException { testNoNulCharacters("[", "["); } + @Test public void testNoNulCharactersThreeChars() throws IOException { testNoNulCharacters("ABC", "ABC"); } @Test public void testNoNulCharactersXml() throws IOException { - testNoNulCharacters("X", - "\"X\""); + testNoNulCharacters( + "X", "\"X\""); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/DefaultLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/DefaultLayoutTest.java new file mode 100644 index 00000000000..9bfb563f1d2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/DefaultLayoutTest.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.appender; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; + +class DefaultLayoutTest { + @Test + void testDefaultLayout() { + PrintStream standardOutput = System.out; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + System.setOut(new PrintStream(baos)); + try { + Logger log = LogManager.getLogger(getClass()); + log.fatal("This is a fatal message"); + log.error("This is an error message"); + log.warn("This is a warning message"); + + String actualOutput = new String(baos.toByteArray(), Charset.defaultCharset()); + assertTrue(actualOutput.contains("FATAL This is a fatal message" + System.lineSeparator())); + assertTrue(actualOutput.contains("ERROR This is an error message" + System.lineSeparator())); + assertFalse(actualOutput.contains("This is a warning message")); + } finally { + System.setOut(standardOutput); + } + } +} 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..111044ff3ef --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.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 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 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.Test; + +public class FailoverAppenderTest { + + @Test + @LoggerContextSource("log4j-failover.xml") + void testFailover(final LoggerContext context, @Named("List") final ListAppender app) { + final Logger logger = context.getLogger("LoggerTest"); + logger.error("This is a test"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals(1, events.size(), "Incorrect number of events. Should be 1 is " + events.size()); + app.clear(); + logger.error("This is a test"); + events = app.getEvents(); + assertNotNull(events); + assertEquals(1, events.size(), "Incorrect number of events. Should be 1 is " + events.size()); + } + + @Test + @LoggerContextSource("log4j-failover.xml") + void testRecovery( + final LoggerContext context, + @Named("List") final ListAppender app, + @Named("Once") final FailOnceAppender foApp) + throws Exception { + final Logger onceLogger = context.getLogger("Once"); + onceLogger.error("Fail once"); + onceLogger.error("Fail again"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals(2, events.size(), "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(0, events.size(), "Did not recover"); + events = foApp.drainEvents(); + assertEquals(2, events.size(), "Incorrect number of events in primary appender"); + } + + @Test + @LoggerContextSource("log4j-failover-location.xml") + void testRequiresLocation(final LoggerContext context) { + final FailoverAppender appender = context.getConfiguration().getAppender("Failover"); + assertTrue(appender.requiresLocation()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverFailedPrimaryAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverFailedPrimaryAppenderTest.java new file mode 100644 index 00000000000..9c39204b166 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverFailedPrimaryAppenderTest.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.appender; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +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.LoggerContextRule; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +/** + * + */ +public class FailoverFailedPrimaryAppenderTest { + private ListAppender app; + private FailOnceAppender foApp; + private Logger logger; + private Logger onceLogger; + + @ClassRule + public static LoggerContextRule init = new LoggerContextRule("log4j-failover.xml"); + + @Before + public void setUp() { + app = init.getListAppender("List"); + foApp = init.getAppender("Once"); + logger = init.getLogger("LoggerTest"); + onceLogger = init.getLogger("Once"); + } + + @After + public void tearDown() { + if (app != null) { + app.clear(); + } + } + + @Test + public void testFailover() { + logger.error("This is a test"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals("Incorrect number of events. Should be 1 is " + events.size(), 1, events.size()); + app.clear(); + logger.error("This is a test"); + events = app.getEvents(); + assertNotNull(events); + assertEquals("Incorrect number of events. Should be 1 is " + events.size(), 1, events.size()); + } + + @Test + public void testRecovery() throws Exception { + onceLogger.error("Fail once"); + onceLogger.error("Fail again"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals("Incorrect number of events. Should be 2 is " + events.size(), 2, events.size()); + app.clear(); + Thread.sleep(1100); + onceLogger.error("Fail after recovery interval"); + onceLogger.error("Second log message"); + events = app.getEvents(); + assertEquals("Did not recover", 0, events.size()); + events = foApp.drainEvents(); + assertEquals("Incorrect number of events in primary appender", 2, events.size()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..b4fdfc03478 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.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.appender; + +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.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; + +class FileAppenderBuilderTest { + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1620 + */ + @Test + void testDefaultImmediateFlush() { + assertTrue(FileAppender.newBuilder().isImmediateFlush()); + } + + /** + * Tests whether a missing name or file name causes the builder to return + * {@code null}. + */ + @Test + void testConstraints() { + final AtomicInteger counter = new AtomicInteger(); + final StatusListener listener = new StatusListener() { + + @Override + public void close() {} + + @Override + public void log(final StatusData data) { + counter.incrementAndGet(); + } + + @Override + public Level getStatusLevel() { + return Level.ERROR; + } + }; + try { + StatusLogger.getLogger().registerListener(listener); + FileAppender appender = FileAppender.newBuilder().build(); + assertNull(appender); + assertTrue(counter.getAndSet(0) > 0); + appender = FileAppender.newBuilder() + .setFileName("target/FileAppenderBuilderTest.log") + .build(); + assertNull(appender); + assertTrue(counter.getAndSet(0) > 0); + appender = FileAppender.newBuilder().setName("FILE").build(); + assertNull(appender); + assertTrue(counter.getAndSet(0) > 0); + appender = FileAppender.newBuilder() + .setName("FILE") + .setFileName("target/FileAppenderBuilderTest.log") + .build(); + assertNotNull(appender); + assertEquals(0, counter.get()); + } catch (NullPointerException e) { + // thrown if no filename is provided + fail(e); + } finally { + StatusLogger.getLogger().removeListener(listener); + } + } +} 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..6e3118628ff --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.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.appender; + +import static org.apache.logging.log4j.util.Unbox.box; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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.Assumptions.assumeTrue; + +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 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.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Tests {@link FileAppender}. + */ +@CleanUpDirectories(FileAppenderPermissionsTest.DIR) +class FileAppenderPermissionsTest { + + static final String DIR = "target/permissions1"; + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j2.debug", "true"); + assumeTrue(FileUtils.isFilePosixAttributeViewSupported()); + } + + @ParameterizedTest + @CsvSource({"rwxrwxrwx,true,2", "rw-r--r--,false,3", "rw-------,true,4", "rw-rw----,false,5"}) + 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(0, curLen, "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"}) + 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(0, curLen, 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() { + // 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..2867c9a758a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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 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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Tests {@link FileAppender}. + */ +@CleanUpFiles(FileAppenderTest.FILE_NAME) +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 + static void cleanupClass() { + assertFalse(AbstractManager.hasManager(FILE_NAME), "Manager for " + FILE_NAME + " not removed"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testAppender(final boolean createOnDemand) throws Exception { + final int logEventCount = 1; + writer(false, logEventCount, "test", createOnDemand, false); + verifyFile(logEventCount); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testLazyCreate(final boolean createOnDemand) { + 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}) + 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}) + void testLockingAppender(final boolean createOnDemand) throws Exception { + final int logEventCount = 1; + writer(true, logEventCount, "test", createOnDemand, false); + verifyFile(logEventCount); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testMultipleAppenderThreads(final boolean createOnDemand) throws Exception { + testMultipleLockingAppenderThreads(false, THREADS, createOnDemand); + } + + private void testMultipleLockingAppenderThreads( + final boolean lock, final int threadCount, final boolean createOnDemand) throws 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}) + void testMultipleLockingAppenders(final boolean createOnDemand) throws Exception { + testMultipleLockingAppenderThreads(true, THREADS, createOnDemand); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + @Disabled + 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( + final 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/FixedHostResolver.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FixedHostResolver.java new file mode 100644 index 00000000000..894d710a6a2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FixedHostResolver.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; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.logging.log4j.core.net.TcpSocketManager; + +/** + * {@link TcpSocketManager.HostResolver} implementation always resolving to the given list of {@link #addresses}. + */ +final class FixedHostResolver extends TcpSocketManager.HostResolver { + + private final List addresses; + + private FixedHostResolver(final List addresses) { + this.addresses = addresses; + } + + static FixedHostResolver ofServers(final LineReadingTcpServer... servers) { + final List addresses = Arrays.stream(servers) + .map(server -> (InetSocketAddress) server.getServerSocket().getLocalSocketAddress()) + .collect(Collectors.toList()); + return new FixedHostResolver(addresses); + } + + @Override + public List resolveHost(final String ignoredHost, final int ignoredPort) { + return addresses; + } +} diff --git a/log4j-core/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 similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java index 1c7b2375863..fd9a838c387 100644 --- a/log4j-core/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 @@ -1,29 +1,29 @@ /* * 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.logging.log4j.core.appender; import java.io.Serializable; 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.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; @@ -39,8 +39,13 @@ public class HangingAppender extends AbstractAppender { 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); + public HangingAppender( + final String name, + final long delay, + final long startupDelay, + final long shutdownDelay, + final Property[] properties) { + super(name, null, null, true, Property.EMPTY_ARRAY); this.delay = delay; this.startupDelay = startupDelay; this.shutdownDelay = shutdownDelay; @@ -57,15 +62,13 @@ public void append(final LogEvent event) { @PluginFactory public static HangingAppender createAppender( - @PluginAttribute("name") - @Required(message = "No name provided for HangingAppender") - final String name, + @PluginAttribute("name") @Required(message = "No name provided for HangingAppender") final String name, @PluginAttribute("delay") final long delay, @PluginAttribute("startupDelay") final long startupDelay, @PluginAttribute("shutdownDelay") final long shutdownDelay, @PluginElement("Layout") final Layout layout, @PluginElement("Filter") final Filter filter) { - return new HangingAppender(name, delay, startupDelay, shutdownDelay); + return new HangingAppender(name, delay, startupDelay, shutdownDelay, null); } @Override diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.java new file mode 100644 index 00000000000..2680961d19c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderBuilderTest.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.appender; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.MalformedURLException; +import java.net.URL; +import org.apache.logging.log4j.Level; +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.Property; +import org.apache.logging.log4j.core.layout.JsonLayout; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +class HttpAppenderBuilderTest { + + private HttpAppender.Builder getBuilder() { + Configuration mockConfig = new DefaultConfiguration(); + return HttpAppender.newBuilder().setConfiguration(mockConfig).setName("TestHttpAppender"); // Name is required + } + + @Test + @UsingStatusListener + void testBuilderWithoutUrl(final ListStatusListener listener) throws Exception { + HttpAppender appender = HttpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setName("TestAppender") + .setLayout(JsonLayout.createDefaultLayout()) // Providing a layout here + .build(); + + assertThat(listener.findStatusData(Level.ERROR)) + .anyMatch(statusData -> + statusData.getMessage().getFormattedMessage().contains("HttpAppender requires URL to be set.")); + } + + @Test + @UsingStatusListener + void testBuilderWithUrlAndWithoutLayout(final ListStatusListener listener) throws Exception { + HttpAppender appender = HttpAppender.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setName("TestAppender") + .setUrl(new URL("http://localhost:8080/logs")) + .build(); + + assertThat(listener.findStatusData(Level.ERROR)).anyMatch(statusData -> statusData + .getMessage() + .getFormattedMessage() + .contains("HttpAppender requires a layout to be set.")); + } + + @Test + void testBuilderWithValidConfiguration() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + + HttpAppender.Builder builder = getBuilder().setUrl(url).setLayout(layout); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with valid configuration."); + } + + @Test + void testBuilderWithCustomMethod() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + String customMethod = "PUT"; + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setMethod(customMethod); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with a custom HTTP method."); + } + + @Test + void testBuilderWithHeaders() throws Exception { + URL url = new URL("http://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + Property[] headers = new Property[] { + Property.createProperty("Header1", "Value1"), Property.createProperty("Header2", "Value2") + }; + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setHeaders(headers); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with headers."); + } + + @Test + void testBuilderWithSslConfiguration() throws Exception { + URL url = new URL("https://example.com"); + Layout layout = JsonLayout.createDefaultLayout(); + + // Use real SslConfiguration instead of Mockito mock + SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(null, null, null, false); + + HttpAppender.Builder builder = + getBuilder().setUrl(url).setLayout(layout).setSslConfiguration(sslConfig); + + HttpAppender appender = builder.build(); + assertNotNull(appender, "HttpAppender should be created with SSL configuration."); + } + + @Test + void testBuilderWithInvalidUrl() { + assertThrows(MalformedURLException.class, () -> new URL("invalid-url")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java new file mode 100644 index 00000000000..a4df56e5074 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java @@ -0,0 +1,325 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +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.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.SslKeyStoreConstants; +import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +/* Fails often on Windows, for example: +[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.20.1:test (default-test) on project log4j-core: There are test failures. +[ERROR] +[ERROR] Please refer to C:\vcs\git\apache\logging\logging-log4j2\log4j-core\target\surefire-reports for the individual test results. +[ERROR] Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream. +[ERROR] ExecutionException The forked VM terminated without properly saying goodbye. VM crash or System.exit called? +[ERROR] Command was cmd.exe /X /C ""C:\Program Files\Java\jdk1.8.0_152\jre\bin\java" -Xms256m -Xmx1024m -jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112\surefirebooter1486874613063862199.jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112 2018-01-23T11-03-18_847-jvmRun6 surefire6375637980242546356tmp surefire_441335531705722358735tmp" +[ERROR] Process Exit Code: 0 +[ERROR] Crashed tests: +[ERROR] org.apache.logging.log4j.core.appender.HttpAppenderTest +[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: ExecutionException The forked VM terminated without properly saying goodbye. VM crash or System.exit called? +[ERROR] Command was cmd.exe /X /C ""C:\Program Files\Java\jdk1.8.0_152\jre\bin\java" -Xms256m -Xmx1024m -jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112\surefirebooter1486874613063862199.jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112 2018-01-23T11-03-18_847-jvmRun6 surefire6375637980242546356tmp surefire_441335531705722358735tmp" +[ERROR] Process Exit Code: 0 +[ERROR] Crashed tests: +[ERROR] org.apache.logging.log4j.core.appender.HttpAppenderTest +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.awaitResultsDone(ForkStarter.java:496) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.runSuitesForkPerTestSet(ForkStarter.java:443) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:295) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:246) +[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1124) +[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:954) +[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:832) +[ERROR] at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134) +[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208) +[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:154) +[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:146) +[ERROR] at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117) +[ERROR] at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81) +[ERROR] at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51) +[ERROR] at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128) +[ERROR] at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:309) +[ERROR] at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:194) +[ERROR] at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:107) +[ERROR] at org.apache.maven.cli.MavenCli.execute(MavenCli.java:955) +[ERROR] at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:290) +[ERROR] at org.apache.maven.cli.MavenCli.main(MavenCli.java:194) +[ERROR] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) +[ERROR] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) +[ERROR] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) +[ERROR] at java.lang.reflect.Method.invoke(Method.java:498) +[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289) +[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229) +[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415) +[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356) +[ERROR] Caused by: org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called? +[ERROR] Command was cmd.exe /X /C ""C:\Program Files\Java\jdk1.8.0_152\jre\bin\java" -Xms256m -Xmx1024m -jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112\surefirebooter1486874613063862199.jar C:\Users\ggregory\AppData\Local\Temp\surefire22320217597495112 2018-01-23T11-03-18_847-jvmRun6 surefire6375637980242546356tmp surefire_441335531705722358735tmp" +[ERROR] Process Exit Code: 0 +[ERROR] Crashed tests: +[ERROR] org.apache.logging.log4j.core.appender.HttpAppenderTest +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:686) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:535) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.access$700(ForkStarter.java:116) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter$2.call(ForkStarter.java:431) +[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter$2.call(ForkStarter.java:408) +[ERROR] at java.util.concurrent.FutureTask.run(FutureTask.java:266) +[ERROR] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) +[ERROR] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) +[ERROR] at java.lang.Thread.run(Thread.java:748) +[ERROR] +[ERROR] -> [Help 1] +[ERROR] +[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. +[ERROR] Re-run Maven using the -X switch to enable full debug logging. +[ERROR] +[ERROR] For more information about the errors and possible solutions, please read the following articles: +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException +[ERROR] +[ERROR] After correcting the problems, you can resume the build with the command +[ERROR] mvn -rf :log4j-core + */ +@DisabledOnOs(OS.WINDOWS) +class HttpAppenderTest { + + private static final Configuration CONFIGURATION = new DefaultConfiguration(); + + private static final String LOG_MESSAGE = "Hello, world!"; + + private static Log4jLogEvent createLogEvent() { + return Log4jLogEvent.newBuilder() + .setLoggerName(HttpAppenderTest.class.getName()) + .setLoggerFqcn(HttpAppenderTest.class.getName()) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage(LOG_MESSAGE)) + .build(); + } + + private final ResponseDefinitionBuilder SUCCESS_RESPONSE = aResponse() + .withStatus(201) + .withHeader("Content-Type", "application/json") + .withBody("{\"status\":\"created\"}"); + + private final ResponseDefinitionBuilder FAILURE_RESPONSE = aResponse() + .withStatus(400) + .withHeader("Content-Type", "application/json") + .withBody("{\"status\":\"error\"}"); + + private static final JavaLookup JAVA_LOOKUP = new JavaLookup(); + + @RegisterExtension + static final WireMockExtension WIRE_MOCK = WireMockExtension.newInstance() + .options(wireMockConfig() + .dynamicPort() + .dynamicHttpsPort() + .keystorePath(SslKeyStoreConstants.KEYSTORE_LOCATION) + .keystorePassword(String.valueOf(SslKeyStoreConstants.KEYSTORE_PWD())) + .keyManagerPassword(String.valueOf(SslKeyStoreConstants.KEYSTORE_PWD())) + .keystoreType(SslKeyStoreConstants.KEYSTORE_TYPE)) + .build(); + + private static URL wireMockUrl(final String path, final boolean secure, final boolean portMangled) + throws MalformedURLException { + final String scheme = secure ? "https" : "http"; + int port = secure ? WIRE_MOCK.getHttpsPort() : WIRE_MOCK.getPort(); + if (portMangled) { + port++; + } + final String url = String.format("%s://localhost:%d%s", scheme, port, path); + return new URL(url); + } + + @Test + void testAppend() throws Exception { + WIRE_MOCK.stubFor(post(urlEqualTo("/test/log4j/")).willReturn(SUCCESS_RESPONSE)); + + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setUrl(wireMockUrl("/test/log4j/", false, false)) + .build(); + appender.append(createLogEvent()); + + WIRE_MOCK.verify(postRequestedFor(urlEqualTo("/test/log4j/")) + .withHeader("Host", containing("localhost")) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(containing("\"message\" : \"" + LOG_MESSAGE + "\""))); + } + + @Test + void testAppendHttps() throws Exception { + WIRE_MOCK.stubFor(post(urlEqualTo("/test/log4j/")).willReturn(SUCCESS_RESPONSE)); + + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setUrl(wireMockUrl("/test/log4j/", true, false)) + .setSslConfiguration(SslConfiguration.createSSLConfiguration( + null, + KeyStoreConfiguration.createKeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + SslKeyStoreConstants.KEYSTORE_PWD(), + null, + null, + SslKeyStoreConstants.KEYSTORE_TYPE, + null), + TrustStoreConfiguration.createKeyStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + SslKeyStoreConstants.TRUSTSTORE_PWD(), + null, + null, + SslKeyStoreConstants.TRUSTSTORE_TYPE, + null))) + .setVerifyHostname(false) + .build(); + appender.append(createLogEvent()); + + WIRE_MOCK.verify(postRequestedFor(urlEqualTo("/test/log4j/")) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(containing("\"message\" : \"" + LOG_MESSAGE + "\""))); + } + + @Test + void testAppendMethodPut() throws Exception { + WIRE_MOCK.stubFor(put(urlEqualTo("/test/log4j/1234")).willReturn(SUCCESS_RESPONSE)); + + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setIgnoreExceptions(false) + .setMethod("PUT") + .setUrl(wireMockUrl("/test/log4j/1234", false, false)) + .build(); + appender.append(createLogEvent()); + + WIRE_MOCK.verify(putRequestedFor(urlEqualTo("/test/log4j/1234")) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(containing("\"message\" : \"" + LOG_MESSAGE + "\""))); + } + + @Test + void testAppendCustomHeader() throws Exception { + WIRE_MOCK.stubFor(post(urlEqualTo("/test/log4j/")).willReturn(SUCCESS_RESPONSE)); + + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setIgnoreExceptions(false) + .setUrl(wireMockUrl("/test/log4j/", false, false)) + .setHeaders(new Property[] { + Property.createProperty("X-Test", "header value"), + Property.createProperty("X-Runtime", "${java:runtime}") + }) + .build(); + appender.append(createLogEvent()); + + WIRE_MOCK.verify(postRequestedFor(urlEqualTo("/test/log4j/")) + .withHeader("X-Test", equalTo("header value")) + .withHeader("X-Runtime", equalTo(JAVA_LOOKUP.getRuntime())) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(containing("\"message\" : \"" + LOG_MESSAGE + "\""))); + } + + @Test + @UsingStatusListener + void testAppendErrorIgnore(final ListStatusListener statusListener) throws Exception { + WIRE_MOCK.stubFor(post(urlEqualTo("/test/log4j/")).willReturn(FAILURE_RESPONSE)); + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setUrl(wireMockUrl("/test/log4j/", false, false)) + .build(); + appender.append(createLogEvent()); + + WIRE_MOCK.verify(postRequestedFor(urlEqualTo("/test/log4j/")) + .withHeader("Content-Type", containing("application/json")) + .withRequestBody(containing("\"message\" : \"" + LOG_MESSAGE + "\""))); + + final List statusDataList = statusListener.getStatusData().collect(Collectors.toList()); + assertThat(statusDataList).anySatisfy(statusData -> { + assertThat(statusData.getLevel()).isEqualTo(Level.ERROR); + assertThat(statusData.getFormattedStatus()).contains("Unable to send HTTP in appender [Http]"); + }); + } + + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void testAppendError() throws Exception { + WIRE_MOCK.stubFor(post(urlEqualTo("/test/log4j/")).willReturn(FAILURE_RESPONSE)); + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setIgnoreExceptions(false) + .setUrl(wireMockUrl("/test/log4j/", false, false)) + .build(); + final LogEvent logEvent = createLogEvent(); + assertThrows(AppenderLoggingException.class, () -> appender.append(logEvent)); + } + + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void testAppendConnectError() throws Exception { + final Appender appender = HttpAppender.newBuilder() + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) + .setConfiguration(CONFIGURATION) + .setIgnoreExceptions(false) + .setUrl(wireMockUrl("/test/log4j/", false, true)) + .build(); + final LogEvent logEvent = createLogEvent(); + assertThrows(AppenderLoggingException.class, () -> appender.append(logEvent)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..cad32c266fe --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.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; + +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 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.test.appender.InMemoryAppender; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +class InMemoryAppenderTest { + + @Test + void testAppender() { + final Layout layout = PatternLayout.createDefaultLayout(); + final boolean writeHeader = true; + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final String expectedHeader = null; + assertMessage("Test", app, expectedHeader); + } + + @Test + void testHeaderRequested() { + final PatternLayout layout = + PatternLayout.newBuilder().setHeader("HEADERHEADER").build(); + final boolean writeHeader = true; + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final String expectedHeader = "HEADERHEADER"; + assertMessage("Test", app, expectedHeader); + } + + @Test + void testHeaderSuppressed() { + final PatternLayout layout = + PatternLayout.newBuilder().setHeader("HEADERHEADER").build(); + final boolean writeHeader = false; + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final String expectedHeader = null; + assertMessage("Test", app, expectedHeader); + } + + private void assertMessage(final String string, final InMemoryAppender app, final String header) { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("TestLogger") // + .setLoggerFqcn(InMemoryAppenderTest.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Test")) // + .build(); + app.start(); + assertTrue(app.isStarted(), "Appender did not start"); + app.append(event); + app.append(event); + final String msg = app.toString(); + assertNotNull(msg, "No message"); + final String expectedHeader = header == null ? "" : header; + final String expected = expectedHeader + "Test" + Strings.LINE_SEPARATOR + "Test" + Strings.LINE_SEPARATOR; + assertEquals(expected, msg, "Incorrect message: " + msg); + app.stop(); + assertFalse(app.isStarted(), "Appender did not stop"); + } +} 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 80% 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..38f49041fd2 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -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
  * 
@@ -37,8 +37,8 @@ public class Jira739Test { private static final Logger LOG = LogManager.getLogger(Jira739Test.class); public static void main(final String[] args) { - try (final LoggerContext ctx = Configurator.initialize(Jira739Test.class.getName(), - "target/test-classes/LOG4J2-739.xml")) { + try (final LoggerContext ctx = + Configurator.initialize(Jira739Test.class.getName(), "target/test-classes/LOG4J2-739.xml")) { for (int i = 0; i < 10; i++) { LOG.trace("Entering Log4j Example " + i + " times"); LOG.error("Ohh!Failed!"); @@ -46,5 +46,4 @@ public static void main(final String[] args) { } } } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java new file mode 100644 index 00000000000..4d1befb11d4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.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.logging.log4j.core.appender; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jLogEventTest; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.selector.CoreContextSelectors; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.junit.CleanFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.ClockFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests a "complete" JSON file. + */ +@RunWith(Parameterized.class) +@Category(Layouts.Json.class) +public class JsonCompleteFileAppenderTest { + + public JsonCompleteFileAppenderTest(final Class contextSelector) { + this.loggerContextRule = new LoggerContextRule("JsonCompleteFileAppenderTest.xml", contextSelector); + this.cleanFiles = new CleanFiles(logFile); + this.ruleChain = RuleChain.outerRule(cleanFiles).around(loggerContextRule); + } + + @BeforeClass + public static void beforeClass() { + System.setProperty(ClockFactory.PROPERTY_NAME, Log4jLogEventTest.FixedTimeClock.class.getName()); + } + + @AfterClass + public static void afterClass() { + System.clearProperty(ClockFactory.PROPERTY_NAME); + } + + @Parameters(name = "{0}") + public static Class[] getParameters() { + return CoreContextSelectors.CLASSES; + } + + private final File logFile = new File("target", "JsonCompleteFileAppenderTest.log"); + private final LoggerContextRule loggerContextRule; + private final CleanFiles cleanFiles; + + @Rule + public RuleChain ruleChain; + + @Test + public void testFlushAtEndOfBatch() throws Exception { + final Logger logger = this.loggerContextRule.getLogger("com.foo.Bar"); + final String logMsg = "Message flushed with immediate flush=true"; + logger.info(logMsg); + logger.error(logMsg, new IllegalArgumentException("badarg")); + this.loggerContextRule.getLoggerContext().stop(); // stops async thread + + final List lines = Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8); + + final String[] expected = { + "[", // equals + "{", // equals + " \"instant\" : {", // + " \"epochSecond\" : 1234567,", // + " \"nanoOfSecond\" : 890000000", // + " },", // + " \"thread\" : \"main\",", // + " \"level\" : \"INFO\",", // + " \"loggerName\" : \"com.foo.Bar\",", // + " \"message\" : \"Message flushed with immediate flush=true\",", // + " \"endOfBatch\" : false,", // + " \"loggerFqcn\" : \"org.apache.logging.log4j.spi.AbstractLogger\",", // + }; + for (int i = 0; i < expected.length; i++) { + final String line = lines.get(i); + assertTrue( + "line " + i + " incorrect: [" + line + "], does not contain: [" + expected[i] + ']', + line.contains(expected[i])); + } + final String location = "testFlushAtEndOfBatch"; + assertFalse("no location", lines.get(0).contains(location)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java new file mode 100644 index 00000000000..9f4028423a6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.awaitility.Awaitility.await; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +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 javax.net.ServerSocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * 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. + *

+ * This class can also be used for secure (i.e., SSL) connections. + * You need to pass the {@link ServerSocketFactory} obtained from {@link SSLContext#getServerSocketFactory()} to the appropriate constructor. + *

+ */ +final class LineReadingTcpServer implements AutoCloseable { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final ServerSocketFactory serverSocketFactory; + + private volatile boolean running; + + private ServerSocket serverSocket; + + private Socket clientSocket; + + private Thread readerThread; + + private final BlockingQueue lines = new LinkedBlockingQueue<>(); + + LineReadingTcpServer() { + this(ServerSocketFactory.getDefault()); + } + + LineReadingTcpServer(final ServerSocketFactory serverSocketFactory) { + this.serverSocketFactory = serverSocketFactory; + } + + synchronized void start(final String name, final int port) throws IOException { + if (!running) { + running = true; + serverSocket = createServerSocket(port); + readerThread = createReaderThread(name); + } + } + + private ServerSocket createServerSocket(final int port) throws IOException { + final ServerSocket serverSocket = + serverSocketFactory.createServerSocket(port, 1, InetAddress.getLoopbackAddress()); + serverSocket.setReuseAddress(true); + serverSocket.setSoTimeout(0); // Zero indicates `accept()` will block indefinitely + await("server socket binding") + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(120, TimeUnit.SECONDS) + .until(() -> serverSocket.getLocalPort() != -1 && serverSocket.isBound()); + 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); + } + } catch (final SSLHandshakeException | EOFException error) { + // Ignore `EOFException`s + if (!(error.getCause() instanceof EOFException)) { + throw error; + } + } + + // 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(); + } + } + + ServerSocket getServerSocket() { + return serverSocket; + } + + List pollLines(@SuppressWarnings("SameParameterValue") final int count) throws InterruptedException { + final List polledLines = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + final String polledLine; + try { + polledLine = pollLine(); + } catch (final TimeoutException timeout) { + final String message = + String.format("timeout while polling for line %d (total needed: %d)", (i + 1), count); + throw new RuntimeException(message, timeout); + } + 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; + } +} 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..f4ca62e9ab2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppenderTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +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.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +/** + * 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" +}) +class MemoryMappedFileAppenderTest { + + @Test + @LoggerContextSource("MemoryMappedFileAppenderTest.xml") + 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") + 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(); + } + assertEquals(272 + 2 * System.lineSeparator().length(), Files.size(logFile), "Expected file size to shrink"); + + final List lines = Files.readAllLines(logFile); + assertThat( + lines, + both(hasSize(2)) + .and( + contains( + "org.apache.logging.log4j.core.appender.MemoryMappedFileAppenderTest.testMemMapLocation(MemoryMappedFileAppenderTest.java:105): Test log1", + "org.apache.logging.log4j.core.appender.MemoryMappedFileAppenderTest.testMemMapLocation(MemoryMappedFileAppenderTest.java:108): Test log2"))); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..2a442f6ea70 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.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 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.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests the MemoryMappedFileManager class. + * + * @since 2.1 + */ +class MemoryMappedFileManagerTest { + + @TempDir + File tempDir; + + @Test + void testRemapAfterInitialMapSizeExceeded() throws IOException { + final int mapSize = 64; // very small, on purpose + final File file = new File(tempDir, "memory-mapped-file.bin"); + + final boolean append = false; + final boolean immediateFlush = false; + try (final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager( + file.getAbsolutePath(), append, immediateFlush, mapSize, null, null)) { + byte[] msg; + for (int i = 0; i < 1000; i++) { + msg = ("Message " + i + "\n").getBytes(); + manager.write(msg, 0, msg.length, false); + } + } + + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line = reader.readLine(); + for (int i = 0; i < 1000; i++) { + assertNotNull(line, "line"); + assertTrue(line.contains("Message " + i), "line incorrect"); + line = reader.readLine(); + } + } + } + + @Test + void testAppendDoesNotOverwriteExistingFile() throws IOException { + final File file = new File(tempDir, "memory-mapped-file.bin"); + + final int initialLength = 4 * 1024; + + // create existing file + try (final FileOutputStream fos = new FileOutputStream(file)) { + fos.write(new byte[initialLength], 0, initialLength); + fos.flush(); + } + assertEquals(initialLength, file.length(), "all flushed to disk"); + + final boolean isAppend = true; + final boolean immediateFlush = false; + try (final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager( + file.getAbsolutePath(), + isAppend, + immediateFlush, + MemoryMappedFileManager.DEFAULT_REGION_LENGTH, + null, + null)) { + manager.writeBytes(new byte[initialLength], 0, initialLength); + } + final int expected = initialLength * 2; + assertEquals(expected, file.length(), "appended, not overwritten"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..e54d97d6520 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.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.core.appender; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.Optional; +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.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +/** + * Tests {@link OutputStreamAppender}. + */ +class OutputStreamAppenderTest { + + private static final String TEST_MSG = "FOO ERROR"; + + public String testName; + + private String getName(final OutputStream out) { + return out.getClass().getSimpleName() + "." + testName; + } + + /** + * Tests that you can add an output stream appender dynamically. + */ + private 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); + ConfigurationTestUtils.updateLoggers(appender, config); + } + + @Test + void testBuildFilter() { + final NoMarkerFilter filter = NoMarkerFilter.newBuilder().build(); + // @formatter:off + final OutputStreamAppender.Builder builder = + OutputStreamAppender.newBuilder().setName("test").setFilter(filter); + // @formatter:on + assertEquals(filter, builder.getFilter()); + final OutputStreamAppender appender = builder.build(); + assertEquals(filter, appender.getFilter()); + } + + @Test + void testOutputStreamAppenderToBufferedOutputStream() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final OutputStream os = new BufferedOutputStream(out); + final String name = getName(out); + final Logger logger = LogManager.getLogger(name); + addAppender(os, name); + logger.error(TEST_MSG); + final String actual = out.toString(); + assertTrue(actual.contains(TEST_MSG), actual); + } + + @Test + void testOutputStreamAppenderToByteArrayOutputStream() { + final OutputStream out = new ByteArrayOutputStream(); + final String name = getName(out); + final Logger logger = LogManager.getLogger(name); + addAppender(out, name); + logger.error(TEST_MSG); + final String actual = out.toString(); + assertTrue(actual.contains(TEST_MSG), actual); + } + + /** + * Validates that the code pattern we use to add an appender on the fly + * works with a basic appender that is not the new OutputStream appender or + * new Writer appender. + */ + @Test + void testUpdatePatternWithFileAppender() { + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + // @formatter:off + final Appender appender = FileAppender.newBuilder() + .setFileName("target/" + getClass().getName() + ".log") + .setAppend(false) + .setName("File") + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(4000) + .setConfiguration(config) + .build(); + // @formatter:on + appender.start(); + config.addAppender(appender); + ConfigurationTestUtils.updateLoggers(appender, config); + LogManager.getLogger().error("FOO MSG"); + } + + @BeforeEach + public void setup(TestInfo testInfo) { + Optional testMethod = testInfo.getTestMethod(); + testMethod.ifPresent(method -> this.testName = method.getName()); + } +} 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..64c0a08dbe1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +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 org.junitpioneer.jupiter.SetSystemProperty; + +/** + * OutputStreamManager Tests. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class OutputStreamManagerTest { + + @Test + @LoggerContextSource("multipleIncompatibleAppendersTest.xml") + void narrow(final LoggerContext context) { + final Logger logger = context.getLogger(OutputStreamManagerTest.class); + logger.info("test"); + StatusLogger statusLogger = StatusLogger.getLogger(); + final List events = statusLogger.getStatusData(); + assertThat(events).isNotEmpty(); + StatusData event = events.get(0); + if (event.getMessage().getFormattedMessage().contains("WindowsAnsiOutputStream")) { + event = events.get(1); + } + assertThat(event.getLevel()).isEqualTo(Level.ERROR); + assertThat(event.getMessage().getFormattedMessage()) + .isEqualTo( + "Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender for element RollingRandomAccessFile"); + assertThat(event.getThrowable()) + .isNotNull() + .asString() + .isEqualTo( + "org.apache.logging.log4j.core.config.ConfigurationException: Configuration has multiple incompatible Appenders pointing to the same resource 'target/multiIncompatibleAppender.log'"); + } + + @Test + void testOutputStreamAppenderFlushClearsBufferOnException() { + final IOException exception = new IOException(); + final OutputStream throwingOutputStream = new OutputStream() { + @Override + public void write(final int b) throws IOException { + throw exception; + } + }; + + final int bufferSize = 3; + final OutputStreamManager outputStreamManager = + new OutputStreamManager(throwingOutputStream, "test", null, false, bufferSize); + + for (int i = 0; i < bufferSize - 1; i++) { + outputStreamManager.getByteBuffer().put((byte) 0); + } + + assertEquals(1, outputStreamManager.getByteBuffer().remaining()); + + final 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 82% 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 index 4e2209fa955..6e9a5761a1a 100644 --- 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender; import java.text.DecimalFormat; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; @@ -41,8 +40,8 @@ public class ProgressConsoleTest { public static void main(final String[] args) { // src/test/resources/log4j2-console-progress.xml // target/test-classes/log4j2-progress-console.xml - try (final LoggerContext ctx = Configurator.initialize(ProgressConsoleTest.class.getName(), - "target/test-classes/log4j2-progress-console.xml")) { + try (final LoggerContext ctx = Configurator.initialize( + ProgressConsoleTest.class.getName(), "target/test-classes/log4j2-progress-console.xml")) { for (double i = 0; i <= 1; i = i + 0.05) { updateProgress(i); try { 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..0371bbdb2ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppenderTest.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; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNotNull; + +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.core.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; + +/** + * 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..fa8ccae5d50 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.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.core.appender; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +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; + +/** + * Tests the RandomAccessFileManager class. + */ +class RandomAccessFileManagerTest { + + @TempDir + File tempDir; + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.RandomAccessFileManager#writeBytes(byte[], int, int)}. + */ + @Test + 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(); + 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 + 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 RandomAccessFileManager manager = new RandomAccessFileManager( + null, raf, file.getName(), os, RandomAccessFileManager.DEFAULT_BUFFER_SIZE, null, null, true); + + final int size = RandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1; + 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()); + + manager.flush(); + assertEquals(size, raf.length()); // all data written to file now + } + } + + @Test + 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); + + 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 + 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 RandomAccessFileManager manager = + new RandomAccessFileManager(null, raf, file.getName(), os, bufferSize, null, null, true); + + final int size = bufferSize * 3 + 1; + 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()); + + manager.flush(); + assertEquals(size, raf.length()); // all data written to file now + } + } + + @Test + 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 (final 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..261c2a2f324 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.core.util.Builder; +import org.junit.jupiter.api.Test; + +class ReconfigureAppenderTest { + private RollingFileAppender appender; + + @Test + 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(...). + final 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 { + final Field field = AbstractManager.class.getDeclaredField("MAP"); + field.setAccessible(true); + + // Retrieve the map itself. + final 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() { + final 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() { + final 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. + final LoggerContext logger_context = Configurator.initialize(config_builder.build()); + + // Retrieve the logger. + final Logger logger = (Logger) LogManager.getLogger(this.getClass()); + + final Builder pattern_builder = + PatternLayout.newBuilder().setPattern("[%d{dd-MM-yy HH:mm:ss}] %p %m %throwable %n"); + + final 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/ScriptAppenderSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java new file mode 100644 index 00000000000..99545656641 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.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.appender; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.MarkerManager; +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.util.Constants; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "Groovy, Javascript") +class ScriptAppenderSelectorTest { + + @Test + @LoggerContextSource("log4j-appender-selector-javascript.xml") + void testJavaScriptSelector(final Configuration config) { + verify(config); + } + + @Test + @LoggerContextSource("log4j-appender-selector-groovy.xml") + void testGroovySelector(final Configuration config) { + verify(config); + } + + static void verify(final Configuration config) { + assertNull(config.getAppender("List1"), "List1 appender should not be initialized"); + assertNull(config.getAppender("List2"), "List2 appender should not be initialized"); + final ListAppender listAppender = config.getAppender("SelectIt"); + assertNotNull(listAppender); + final ExtendedLogger logger = config.getLoggerContext().getLogger(ScriptAppenderSelectorTest.class); + logger.error("Hello"); + assertThat(listAppender.getEvents(), hasSize(1)); + logger.error("World"); + assertThat(listAppender.getEvents(), hasSize(2)); + logger.error(MarkerManager.getMarker("HEXDUMP"), "DEADBEEF"); + assertThat(listAppender.getEvents(), hasSize(3)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.java new file mode 100644 index 00000000000..d43abc5ab24 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderAsyncTest.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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.Iterator; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.test.AvailablePortFinder; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.smtp.SimpleSmtpServer; +import org.apache.logging.log4j.core.test.smtp.SmtpMessage; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +public class SmtpAppenderAsyncTest { + + private static int PORT; + + private SimpleSmtpServer smtpServer; + + @BeforeClass + public static void setupClass() { + PORT = AvailablePortFinder.getNextAvailable(); + System.setProperty("smtp.port", String.valueOf(PORT)); + } + + @Before + public void setup() { + smtpServer = SimpleSmtpServer.start(PORT); + } + + @Rule + public LoggerContextRule ctx = new LoggerContextRule("SmtpAppenderAsyncTest.xml"); + + @Test + public void testSync() { + testSmtpAppender(ctx.getLogger("sync")); + } + + @Test + public void testAsync() { + testSmtpAppender(ctx.getLogger("async")); + } + + private void testSmtpAppender(final Logger logger) { + ThreadContext.put("MDC1", "mdc1"); + logger.error("the message"); + ctx.getLoggerContext().stop(); + smtpServer.stop(); + + assertEquals(1, smtpServer.getReceivedEmailSize()); + final Iterator messages = smtpServer.getReceivedEmail(); + final SmtpMessage email = messages.next(); + + assertEquals("to@example.com", email.getHeaderValue("To")); + assertEquals("from@example.com", email.getHeaderValue("From")); + assertEquals("[mdc1]", email.getHeaderValue("Subject")); + + final String body = email.getBody(); + if (!body.contains("Body:[mdc1]")) { + fail(body); + } + } + + @After + public void teardown() { + if (smtpServer != null) { + smtpServer.stop(); + } + } + + @AfterClass + public static void teardownClass() { + System.clearProperty("smtp.port"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderTest.java new file mode 100644 index 00000000000..1ad3d8f8d2d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SmtpAppenderTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.util.Iterator; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import org.apache.logging.log4j.Level; +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.net.MimeMessageBuilder; +import org.apache.logging.log4j.core.net.SmtpManager; +import org.apache.logging.log4j.core.test.AvailablePortFinder; +import org.apache.logging.log4j.core.test.smtp.SimpleSmtpServer; +import org.apache.logging.log4j.core.test.smtp.SmtpMessage; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("Smtp") +class SmtpAppenderTest { + + private static final String HOST = "localhost"; + + @Test + void testMessageFactorySetFrom() throws MessagingException { + final MimeMessageBuilder builder = new MimeMessageBuilder(null); + final String address = "testing@example.com"; + + assertNull(builder.build().getFrom()); + + builder.setFrom(null); + Address[] array = null; + final Address addr = InternetAddress.getLocalAddress(null); + if (addr != null) { + array = new Address[] {addr}; + } + assertArrayEquals(array, builder.build().getFrom()); + + builder.setFrom(address); + assertArrayEquals( + new Address[] {new InternetAddress(address)}, builder.build().getFrom()); + } + + @Test + void testMessageFactorySetReplyTo() throws MessagingException { + final MimeMessageBuilder builder = new MimeMessageBuilder(null); + final String addresses = "testing1@example.com,testing2@example.com"; + + assertNull(builder.build().getReplyTo()); + + builder.setReplyTo(null); + assertNull(builder.build().getReplyTo()); + + builder.setReplyTo(addresses); + assertArrayEquals(InternetAddress.parse(addresses), builder.build().getReplyTo()); + } + + @Test + void testMessageFactorySetRecipients() throws MessagingException { + final MimeMessageBuilder builder = new MimeMessageBuilder(null); + final String addresses = "testing1@example.com,testing2@example.com"; + + assertNull(builder.build().getRecipients(Message.RecipientType.TO)); + + builder.setRecipients(Message.RecipientType.TO, null); + assertNull(builder.build().getRecipients(Message.RecipientType.TO)); + + builder.setRecipients(Message.RecipientType.TO, addresses); + assertArrayEquals(InternetAddress.parse(addresses), builder.build().getRecipients(Message.RecipientType.TO)); + } + + @Test + void testMessageFactorySetSubject() throws MessagingException { + final MimeMessageBuilder builder = new MimeMessageBuilder(null); + final String subject = "Test Subject"; + + assertNull(builder.build().getSubject()); + + builder.setSubject(null); + assertNull(builder.build().getSubject()); + + builder.setSubject(subject); + assertEquals(subject, builder.build().getSubject()); + } + + @Test + void testDelivery() { + final String subjectKey = getClass().getName(); + final String subjectValue = "SubjectValue1"; + ThreadContext.put(subjectKey, subjectValue); + final int smtpPort = AvailablePortFinder.getNextAvailable(); + final SmtpAppender appender = SmtpAppender.newBuilder() + .setName("Test") + .setTo("to@example.com") + .setCc("cc@example.com") + .setBcc("bcc@example.com") + .setFrom("from@example.com") + .setReplyTo("replyTo@example.com") + .setSubject("Subject Pattern %X{" + subjectKey + "} %maxLen{%m}{10}") + .setSmtpHost(HOST) + .setSmtpPort(smtpPort) + .setBufferSize(3) + .build(); + assertNotNull(appender); + assertInstanceOf(SmtpManager.class, appender.getManager()); + appender.start(); + + final LoggerContext context = LoggerContext.getContext(); + final Logger root = context.getLogger("SMTPAppenderTest"); + root.addAppender(appender); + root.setAdditive(false); + root.setLevel(Level.DEBUG); + + final SimpleSmtpServer server = SimpleSmtpServer.start(smtpPort); + + root.debug("Debug message #1"); + root.debug("Debug message #2"); + root.debug("Debug message #3"); + root.debug("Debug message #4"); + root.error("Error with exception", new RuntimeException("Exception message")); + root.error("Error message #2"); + + server.stop(); + assertEquals(2, server.getReceivedEmailSize()); + final Iterator messages = server.getReceivedEmail(); + final SmtpMessage email = messages.next(); + + assertEquals("to@example.com", email.getHeaderValue("To")); + assertEquals("cc@example.com", email.getHeaderValue("Cc")); + // assertEquals("bcc@example.com", email.getHeaderValue("Bcc")); // BCC + // can't be tested with Dumpster 1.6 + assertEquals("from@example.com", email.getHeaderValue("From")); + assertEquals("replyTo@example.com", email.getHeaderValue("Reply-To")); + assertEquals("Subject Pattern " + subjectValue + " Error with", email.getHeaderValue("Subject")); + + final String body = email.getBody(); + assertFalse(body.contains("Debug message #1")); + assertTrue(body.contains("Debug message #2")); + assertTrue(body.contains("Debug message #3")); + assertTrue(body.contains("Debug message #4")); + assertTrue(body.contains("Error with exception")); + assertTrue(body.contains("RuntimeException")); + assertTrue(body.contains("Exception message")); + assertFalse(body.contains("Error message #2")); + + final SmtpMessage email2 = messages.next(); + + assertEquals("Subject Pattern " + subjectValue + " Error mess", email2.getHeaderValue("Subject")); + + final String body2 = email2.getBody(); + assertFalse(body2.contains("Debug message #4")); + assertFalse(body2.contains("Error with exception")); + assertTrue(body2.contains("Error message #2")); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java index 85b6b17d478..0a61efa5c25 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,9 +20,9 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.SocketAppenderTest.TcpSocketTestServer; +import org.apache.logging.log4j.core.test.AvailablePortFinder; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -73,5 +73,4 @@ public void testTcpAppenderLargeEncoderBufferSize() throws Exception { public void testTcpAppenderSmallestEncoderBufferSize() throws Exception { SocketAppenderTest.testTcpAppender(tcpServer, logger, 1); } - } diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..e67b2f2d65e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.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.appender; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class SocketAppenderBuilderTest { + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1620 + */ + @Test + void testDefaultImmediateFlush() { + assertTrue( + SocketAppender.newBuilder().isImmediateFlush(), + "Regression of LOG4J2-1620: default value for immediateFlush should be true"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.java new file mode 100644 index 00000000000..619749eacf2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderReconnectTest.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.appender; + +import static org.apache.logging.log4j.core.appender.SslContexts.createSslContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.fail; + +import edu.umd.cs.findbugs.annotations.Nullable; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.net.ssl.SSLContext; +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.Configuration; +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.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.net.TcpSocketManager; +import org.apache.logging.log4j.core.net.TcpSocketManager.HostResolver; +import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests reconnection support of {@link org.apache.logging.log4j.core.appender.SocketAppender}. + */ +class SocketAppenderReconnectTest { + + private static final String CLASS_NAME = SocketAppenderReconnectTest.class.getSimpleName(); + + private static final int EPHEMERAL_PORT = 0; + + private static final String APPENDER_NAME = "TestSocket"; + + /** + * Tests if failures are propagated when reconnection fails. + * + * @see LOG4J2-2829 + */ + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void repeating_reconnect_failures_should_be_propagated() throws Exception { + try (final LineReadingTcpServer server = new LineReadingTcpServer()) { + + // Start the server. + server.start("Main", EPHEMERAL_PORT); + final String serverHost = server.getServerSocket().getInetAddress().getHostAddress(); + final int serverPort = server.getServerSocket().getLocalPort(); + + // Initialize the logger context + final Configuration config = createConfiguration(serverHost, serverPort, null); + try (final LoggerContext loggerContext = createStartedLoggerContext(config)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state. + verifyLoggingSuccess(loggerContext, server, errorHandler); + + // Stop the server, and verify the logging failure. + server.close(); + verifyLoggingFailure(loggerContext, errorHandler); + + // Start the server again, and verify the logging success. + server.start("Main", serverPort); + verifyLoggingSuccess(loggerContext, server, errorHandler); + } + } + } + + /** + * Tests if all the {@link InetSocketAddress}es returned by an {@link HostResolver} is used for fallback on reconnect attempts. + */ + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + 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 Configuration config = createConfiguration( + // Passing dummy host & port, since the resolution is supposed to be performed by the mocked + // host resolver anyway. + "localhost", 0, null); + try (final LoggerContext loggerContext = createStartedLoggerContext(config)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state on the primary server. + verifyLoggingSuccess(loggerContext, primaryServer, errorHandler); + + // Stop the primary server, and verify the logging success due to fallback on to the secondary + // server. + primaryServer.close(); + verifyLoggingSuccess(loggerContext, secondaryServer, errorHandler); + } + + } + + // Reset the host resolver + finally { + TcpSocketManager.setHostResolver(new HostResolver()); + } + } + } + + /** + * Triggers a reconfiguration such that the {@code } and {@code } configuration will be unchanged, but the content they refer to will be updated. + * + * @see LOG4J2-2988 + * @see #2767 + */ + @Test + void key_store_changes_should_be_detected_at_reconfigure( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws Exception { + + // Create the 1st `SSLContext` + final String keyStore1Type = SslKeyStoreConstants.KEYSTORE_TYPE; + final String keyStore1Location = SslKeyStoreConstants.KEYSTORE_LOCATION; + final char[] keyStore1Password = SslKeyStoreConstants.KEYSTORE_PWD(); + final String trustStore1Type = SslKeyStoreConstants.TRUSTSTORE_TYPE; + final String trustStore1Location = SslKeyStoreConstants.TRUSTSTORE_LOCATION; + final char[] trustStore1Password = SslKeyStoreConstants.TRUSTSTORE_PWD(); + final SSLContext sslContext1 = createSslContext( + keyStore1Type, + keyStore1Location, + keyStore1Password, + trustStore1Type, + trustStore1Location, + trustStore1Password); + + // Create the 2nd `SSLContext` + final String keyStore2Type = SslKeyStoreConstants.KEYSTORE2_TYPE; + final String keyStore2Location = SslKeyStoreConstants.KEYSTORE2_LOCATION; + final char[] keyStore2Password = SslKeyStoreConstants.KEYSTORE2_PWD(); + final String trustStore2Type = SslKeyStoreConstants.TRUSTSTORE2_TYPE; + final String trustStore2Location = SslKeyStoreConstants.TRUSTSTORE2_LOCATION; + final char[] trustStore2Password = SslKeyStoreConstants.TRUSTSTORE2_PWD(); + final SSLContext sslContext2 = createSslContext( + keyStore2Type, + keyStore2Location, + keyStore2Password, + trustStore2Type, + trustStore2Location, + trustStore2Password); + + // Ensure that store types are identical. + // We will use these in the `*StoreConfiguration`. + // They need to be same, so that the encapsulating `SslConfiguration` will be unchanged during reconfiguration. + assertThat(keyStore1Type).isEqualTo(keyStore2Type); + + // Stage the key store files using the 1st `SSLContext` + @SuppressWarnings("UnnecessaryLocalVariable") + final String keyStoreType = keyStore1Type; + final Path keyStoreFilePath = tempDir.resolve("keyStore"); + Files.write(keyStoreFilePath, Files.readAllBytes(Paths.get(keyStore1Location))); + final Path keyStorePasswordFilePath = tempDir.resolve("keyStorePassword"); + Files.write(keyStorePasswordFilePath, new String(keyStore1Password).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store files using the 1st `SSLContext` + @SuppressWarnings("UnnecessaryLocalVariable") + final String trustStoreType = trustStore1Type; + final Path trustStoreFilePath = tempDir.resolve("trustStore"); + Files.write(trustStoreFilePath, Files.readAllBytes(Paths.get(trustStore1Location))); + final Path trustStorePasswordFilePath = tempDir.resolve("trustStorePassword"); + Files.write(trustStorePasswordFilePath, new String(trustStore1Password).getBytes(StandardCharsets.UTF_8)); + + // Create servers + try (final LineReadingTcpServer server1 = new LineReadingTcpServer(sslContext1.getServerSocketFactory()); + final LineReadingTcpServer server2 = new LineReadingTcpServer(sslContext2.getServerSocketFactory())) { + + // Start the 1st server + server1.start("1st", EPHEMERAL_PORT); + final String server1Host = + server1.getServerSocket().getInetAddress().getHostAddress(); + final int server1Port = server1.getServerSocket().getLocalPort(); + + // Create the configuration transformer to add the ``, ``, and `` elements + final BiFunction< + ConfigurationBuilder, + AppenderComponentBuilder, + AppenderComponentBuilder> + appenderComponentBuilderTransformer = (configBuilder, appenderComponentBuilder) -> { + final ComponentBuilder keyStoreComponentBuilder = configBuilder + .newComponent("KeyStore") + .addAttribute("type", keyStoreType) + .addAttribute("location", keyStoreFilePath.toString()) + .addAttribute("passwordFile", keyStorePasswordFilePath); + final ComponentBuilder trustStoreComponentBuilder = configBuilder + .newComponent("TrustStore") + .addAttribute("type", trustStoreType) + .addAttribute("location", trustStoreFilePath.toString()) + .addAttribute("passwordFile", trustStorePasswordFilePath); + return appenderComponentBuilder.addComponent(configBuilder + .newComponent("Ssl") + .addAttribute("protocol", "TLS") + .addComponent(keyStoreComponentBuilder) + .addComponent(trustStoreComponentBuilder)); + }; + + // Initialize the logger context + final Configuration config1 = + createConfiguration(server1Host, server1Port, appenderComponentBuilderTransformer); + try (final LoggerContext loggerContext = createStartedLoggerContext(config1)) { + + // Configure the error handler + final BufferingErrorHandler errorHandler = new BufferingErrorHandler(); + loggerContext.getConfiguration().getAppender(APPENDER_NAME).setHandler(errorHandler); + + // Verify the initial working state on the 1st server + verifyLoggingSuccess(loggerContext, server1, errorHandler); + + // Stop the 1st server and start the 2nd one (using different SSL configuration!) on the same port + server1.close(); + server2.start("2nd", server1Port); + + // Stage the key store files using the 2nd `SSLContext` + Files.write(keyStoreFilePath, Files.readAllBytes(Paths.get(keyStore2Location))); + Files.write(keyStorePasswordFilePath, new String(keyStore2Password).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store files using the 2nd `SSLContext` + Files.write(trustStoreFilePath, Files.readAllBytes(Paths.get(trustStore2Location))); + Files.write( + trustStorePasswordFilePath, new String(trustStore2Password).getBytes(StandardCharsets.UTF_8)); + + // Reconfigure the logger context + // + // You might be thinking: + // + // Why don't we simply call `loggerContext.reconfigure()`? + // Why do we need to create the very same configuration twice? + // + // We need to reconfigure the logger context using the very same configuration to test if + // `SslSocketManager` will be able to pick up the key and trust store changes even though the + // `SslConfiguration` is untouched – the whole point of this test and the issue reported in LOG4J2-2988. + // + // `loggerContext.reconfigure()` stops the active configuration (i.e., the programmatically built + // configuration we provided to it), and starts a fresh scan for `log4j2.xml` et al. in the classpath. + // This effectively discards the initially provided configuration. + // + // `loggerContext.reconfigure(loggerContext.getConfiguration())` doesn't work either, since it tries to + // start the configuration it has just stopped and a programmatically built `Configuration` is not + // `Reconfigurable` – yes, this needs to be fixed. + // + // Hence, the only way is to programmatically build the very same configuration, twice, and use the 1st + // one for initialization, and the 2nd one for reconfiguration. + final Configuration config2 = + createConfiguration(server1Host, server1Port, appenderComponentBuilderTransformer); + loggerContext.reconfigure(config2); + + // Verify the working state on the 2nd server + verifyLoggingSuccess(loggerContext, server2, errorHandler); + } + } + } + + private static Configuration createConfiguration( + final String host, + final int port, + @Nullable + final BiFunction< + ConfigurationBuilder, + AppenderComponentBuilder, + AppenderComponentBuilder> + appenderComponentBuilderTransformer) { + + // Create the configuration builder + final ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder() + .setStatusLevel(Level.ERROR) + .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); + + // Create the appender configuration + final AppenderComponentBuilder appenderComponentBuilder = configBuilder + .newAppender(APPENDER_NAME, "Socket") + .addAttribute("host", host) + .addAttribute("port", port) + .addAttribute("ignoreExceptions", false) + .addAttribute("reconnectionDelayMillis", 10) + .addAttribute("immediateFlush", true) + .add(configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m%n")); + final AppenderComponentBuilder transformedAppenderComponentBuilder = appenderComponentBuilderTransformer != null + ? appenderComponentBuilderTransformer.apply(configBuilder, appenderComponentBuilder) + : appenderComponentBuilder; + + // Create the configuration + return configBuilder + .add(transformedAppenderComponentBuilder) + .add(configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(APPENDER_NAME))) + .build(false); + } + + private static final AtomicInteger LOGGER_CONTEXT_COUNTER = new AtomicInteger(); + + private static LoggerContext createStartedLoggerContext(final Configuration configuration) { + final String name = String.format( + "%s-%02d", SocketAppenderReconnectTest.class.getSimpleName(), LOGGER_CONTEXT_COUNTER.getAndIncrement()); + final LoggerContext loggerContext = new LoggerContext(name); + loggerContext.start(configuration); + return loggerContext; + } + + private static void verifyLoggingSuccess( + final LoggerContext loggerContext, + final LineReadingTcpServer server, + final BufferingErrorHandler errorHandler) + throws Exception { + + // Report status + StatusLogger.getLogger().debug("[{}] verifying logging success", CLASS_NAME); + + // Create messages to log + final int messageCount = 2; + assertThat(messageCount) + .as("expecting `messageCount > 1` due to LOG4J2-2829") + .isGreaterThan(1); + final List expectedMessages = IntStream.range(0, messageCount) + .mapToObj(messageIndex -> String.format("m%02d", messageIndex)) + .collect(Collectors.toList()); + + // Log the 1st message + // Due to socket initialization, the first `write()` might need some extra effort + final Logger logger = loggerContext.getRootLogger(); + await("first socket append") + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(120, TimeUnit.SECONDS) + .ignoreExceptions() + .untilAsserted(() -> { + final String message = expectedMessages.get(0); + logger.info(message); + }); + + // Reset the error handler + errorHandler.clear(); + + // Log the rest of the messages + for (int messageIndex = 1; messageIndex < expectedMessages.size(); messageIndex++) { + final String message = expectedMessages.get(messageIndex); + logger.info(message); + } + + // Verify the messages received by the server + final List actualMessages = server.pollLines(messageCount); + assertThat(actualMessages).containsExactlyElementsOf(expectedMessages); + + // Verify the error handler state + assertThat(errorHandler.getBuffer()).isEmpty(); + } + + private static void verifyLoggingFailure( + final LoggerContext loggerContext, final BufferingErrorHandler errorHandler) { + + // Report status + StatusLogger.getLogger().debug("[{}] verifying logging failure", CLASS_NAME); + + // Verify the configuration + final Logger logger = loggerContext.getRootLogger(); + final int retryCount = 3; + assertThat(retryCount) + .as("expecting `retryCount > 1` due to LOG4J2-2829") + .isGreaterThan(1); + + // Verify the failure + for (int i = 0; i < retryCount; i++) { + try { + logger.info("should fail #" + i); + fail("should have failed #" + i); + } catch (final AppenderLoggingException ignored) { + assertThat(errorHandler.getBuffer()).hasSize(2 * (i + 1)); + } + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java index 030fa8049f0..7988c3fbd8c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java @@ -1,32 +1,31 @@ /* * 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.logging.log4j.core.appender; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; - import org.apache.logging.log4j.core.appender.SocketAppenderTest.TcpSocketTestServer; 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.AvailablePortFinder; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.NullOutputStream; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; @@ -79,17 +78,18 @@ public void testSocketOptions() throws IOException { Assert.assertEquals(false, socketOptions.isOobInline()); Assert.assertEquals(false, socketOptions.isReuseAddress()); Assert.assertEquals(false, socketOptions.isTcpNoDelay()); - Assert.assertEquals(Rfc1349TrafficClass.IPTOS_LOWCOST.value(), + Assert.assertEquals( + Rfc1349TrafficClass.IPTOS_LOWCOST.value(), socketOptions.getActualTrafficClass().intValue()); Assert.assertEquals(10000, socketOptions.getReceiveBufferSize().intValue()); Assert.assertEquals(8000, socketOptions.getSendBufferSize().intValue()); Assert.assertEquals(12345, socketOptions.getSoLinger().intValue()); Assert.assertEquals(54321, socketOptions.getSoTimeout().intValue()); // Test live socket - Assert.assertEquals(false, socket.getKeepAlive()); - Assert.assertEquals(false, socket.getOOBInline()); - Assert.assertEquals(false, socket.getReuseAddress()); - Assert.assertEquals(false, socket.getTcpNoDelay()); + Assert.assertFalse(socket.getKeepAlive()); + Assert.assertFalse(socket.getOOBInline()); + Assert.assertFalse(socket.getReuseAddress()); + Assert.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()); @@ -99,7 +99,9 @@ public void testSocketOptions() throws IOException { @Test public void testSocketTrafficClass() throws IOException { - Assume.assumeTrue("Run only on Java 7", System.getProperty("java.specification.version").equals("1.7")); + Assume.assumeTrue( + "Run only on Java 7", + System.getProperty("java.specification.version").equals("1.7")); Assume.assumeFalse("Do not run on Travis CI", "true".equals(System.getenv("TRAVIS"))); final SocketAppender appender = loggerContextRule.getAppender("socket", SocketAppender.class); final TcpSocketManager manager = (TcpSocketManager) appender.getManager(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.java new file mode 100644 index 00000000000..0167194c7b1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSslSocketOptionsTest.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.core.appender; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.net.ssl.SSLContext; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +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.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.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.net.Rfc1349TrafficClass; +import org.apache.logging.log4j.core.net.SocketOptions; +import org.apache.logging.log4j.core.net.SocketPerformancePreferences; +import org.apache.logging.log4j.core.net.SslSocketManager; +import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +class SocketAppenderSslSocketOptionsTest { + + private static final String APPENDER_NAME = "TestSocket"; + + // Socket performance preferences + + private static final int SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH = 100; + + private static final int SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME = 101; + + private static final int SOCKET_PERFORMANCE_PREFERENCE_LATENCY = 102; + + // Socket options + + private static final boolean SOCKET_OPTION_KEEP_ALIVE = false; + + private static final int SOCKET_OPTION_RECEIVE_BUFFER_SIZE = 10_000; + + private static final boolean SOCKET_OPTION_REUSE_ADDRESS = false; + + private static final Rfc1349TrafficClass SOCKET_OPTION_RFC1349_TRAFFIC_CLASS = Rfc1349TrafficClass.IPTOS_LOWCOST; + + private static final int SOCKET_OPTION_SEND_BUFFER_SIZE = 8_000; + + private static final int SOCKET_OPTION_LINGER = 12_345; + + private static final int SOCKET_OPTION_TIMEOUT = 54_321; + + private static final boolean SOCKET_OPTION_TCP_NO_DELAY = false; + + // Key & Trust store + + private static final String KEYSTORE_TYPE = SslKeyStoreConstants.KEYSTORE_TYPE; + + private static final String KEYSTORE_LOCATION = SslKeyStoreConstants.KEYSTORE_LOCATION; + + private static final char[] KEYSTORE_PWD = SslKeyStoreConstants.KEYSTORE_PWD(); + + private static final String TRUSTSTORE_TYPE = SslKeyStoreConstants.TRUSTSTORE_TYPE; + + private static final String TRUSTSTORE_LOCATION = SslKeyStoreConstants.TRUSTSTORE_LOCATION; + + private static final char[] TRUSTSTORE_PWD = SslKeyStoreConstants.TRUSTSTORE_PWD(); + + @Test + void socket_options_should_be_injected(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) + throws Exception { + + // Create the SSL context + final SSLContext sslContext = SslContexts.createSslContext( + KEYSTORE_TYPE, KEYSTORE_LOCATION, KEYSTORE_PWD, TRUSTSTORE_TYPE, TRUSTSTORE_LOCATION, TRUSTSTORE_PWD); + + // Create the server + try (final LineReadingTcpServer server = new LineReadingTcpServer(sslContext.getServerSocketFactory())) { + + // Start the server + server.start("1st", 0); + final int port = server.getServerSocket().getLocalPort(); + + // Create the logger context + final Configuration config = createConfiguration(tempDir, port); + try (final LoggerContext loggerContext = Configurator.initialize(config)) { + + // Extract the socket manager + final SocketAppender appender = loggerContext.getConfiguration().getAppender(APPENDER_NAME); + final SslSocketManager manager = (SslSocketManager) appender.getManager(); + + // Verify socket options + final SocketOptions socketOptions = manager.getSocketOptions(); + assertThat(socketOptions.isKeepAlive()).isEqualTo(SOCKET_OPTION_KEEP_ALIVE); + assertThat(socketOptions.isOobInline()).isNull(); + assertThat(socketOptions.getReceiveBufferSize()).isEqualTo(SOCKET_OPTION_RECEIVE_BUFFER_SIZE); + assertThat(socketOptions.isReuseAddress()).isEqualTo(SOCKET_OPTION_REUSE_ADDRESS); + assertThat(socketOptions.getRfc1349TrafficClass()).isEqualTo(SOCKET_OPTION_RFC1349_TRAFFIC_CLASS); + assertThat(socketOptions.getActualTrafficClass()) + .isEqualTo(SOCKET_OPTION_RFC1349_TRAFFIC_CLASS.value()); + assertThat(socketOptions.getSendBufferSize()).isEqualTo(SOCKET_OPTION_SEND_BUFFER_SIZE); + assertThat(socketOptions.getSoLinger()).isEqualTo(SOCKET_OPTION_LINGER); + assertThat(socketOptions.getSoTimeout()).isEqualTo(SOCKET_OPTION_TIMEOUT); + assertThat(socketOptions.isTcpNoDelay()).isEqualTo(SOCKET_OPTION_TCP_NO_DELAY); + + // Verify socket performance preferences + final SocketPerformancePreferences performancePreferences = socketOptions.getPerformancePreferences(); + assertThat(performancePreferences.getBandwidth()).isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH); + assertThat(performancePreferences.getConnectionTime()) + .isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME); + assertThat(performancePreferences.getLatency()).isEqualTo(SOCKET_PERFORMANCE_PREFERENCE_LATENCY); + } + } + } + + private static Configuration createConfiguration(final Path tempDir, final int port) throws Exception { + + // Create the configuration builder + final ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder() + .setStatusLevel(Level.ERROR) + .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); + + // Stage the key store password file + final Path keyStorePasswordFilePath = tempDir.resolve("keyStorePassword"); + Files.write(keyStorePasswordFilePath, new String(KEYSTORE_PWD).getBytes(StandardCharsets.UTF_8)); + + // Stage the trust store password file + final Path trustStorePasswordFilePath = tempDir.resolve("trustStorePassword"); + Files.write(trustStorePasswordFilePath, new String(TRUSTSTORE_PWD).getBytes(StandardCharsets.UTF_8)); + + // Create the `SocketOptions` element + final ComponentBuilder socketPerformancePreferencesComponentBuilder = configBuilder + .newComponent("SocketPerformancePreferences") + .addAttribute("bandwidth", SOCKET_PERFORMANCE_PREFERENCE_BANDWIDTH) + .addAttribute("connectionTime", SOCKET_PERFORMANCE_PREFERENCE_CONNECTION_TIME) + .addAttribute("latency", SOCKET_PERFORMANCE_PREFERENCE_LATENCY); + final ComponentBuilder socketOptionsComponentBuilder = configBuilder + .newComponent("SocketOptions") + .addAttribute("keepAlive", SOCKET_OPTION_KEEP_ALIVE) + .addAttribute("receiveBufferSize", SOCKET_OPTION_RECEIVE_BUFFER_SIZE) + .addAttribute("reuseAddress", SOCKET_OPTION_REUSE_ADDRESS) + .addAttribute("rfc1349TrafficClass", SOCKET_OPTION_RFC1349_TRAFFIC_CLASS) + .addAttribute("sendBufferSize", SOCKET_OPTION_SEND_BUFFER_SIZE) + .addAttribute("soLinger", SOCKET_OPTION_LINGER) + .addAttribute("soTimeout", SOCKET_OPTION_TIMEOUT) + .addAttribute("tcpNoDelay", SOCKET_OPTION_TCP_NO_DELAY) + .addComponent(socketPerformancePreferencesComponentBuilder); + + // Create the `Ssl` element + final ComponentBuilder keyStoreComponentBuilder = configBuilder + .newComponent("KeyStore") + .addAttribute("type", KEYSTORE_TYPE) + .addAttribute("location", KEYSTORE_LOCATION) + .addAttribute("passwordFile", keyStorePasswordFilePath); + final ComponentBuilder trustStoreComponentBuilder = configBuilder + .newComponent("TrustStore") + .addAttribute("type", TRUSTSTORE_TYPE) + .addAttribute("location", TRUSTSTORE_LOCATION) + .addAttribute("passwordFile", trustStorePasswordFilePath); + final ComponentBuilder sslComponentBuilder = configBuilder + .newComponent("Ssl") + .addAttribute("protocol", "TLS") + .addComponent(keyStoreComponentBuilder) + .addComponent(trustStoreComponentBuilder); + + // Create the `Socket` element + final AppenderComponentBuilder appenderComponentBuilder = configBuilder + .newAppender(APPENDER_NAME, "Socket") + .addAttribute("host", "localhost") + .addAttribute("port", port) + .addAttribute("ignoreExceptions", false) + .addAttribute("reconnectionDelayMillis", 10) + .addAttribute("immediateFlush", true) + .add(configBuilder.newLayout("PatternLayout").addAttribute("pattern", "%m%n")) + .addComponent(socketOptionsComponentBuilder) + .addComponent(sslComponentBuilder); + + // Create the configuration + return configBuilder + .add(appenderComponentBuilder) + .add(configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(APPENDER_NAME))) + .build(false); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java new file mode 100644 index 00000000000..3eaad6533f1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java @@ -0,0 +1,409 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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 com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +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.test.AvailablePortFinder; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.Throwables; +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.Test; + +/** + * + */ +class SocketAppenderTest { + + private static final int PORT = AvailablePortFinder.getNextAvailable(); + private static final int DYN_PORT = AvailablePortFinder.getNextAvailable(); + private static final int ERROR_PORT = AvailablePortFinder.getNextAvailable(); + + private static TcpSocketTestServer tcpServer; + private static UdpSocketTestServer udpServer; + + private final LoggerContext context = LoggerContext.getContext(); + private final Logger logger = context.getLogger(SocketAppenderTest.class.getName()); + + @BeforeAll + static void setupClass() throws Exception { + tcpServer = new TcpSocketTestServer(PORT); + tcpServer.start(); + udpServer = new UdpSocketTestServer(); + udpServer.start(); + LoggerContext.getContext().reconfigure(); + ThreadContext.clearAll(); + } + + @AfterAll + static void cleanupClass() { + tcpServer.shutdown(); + udpServer.shutdown(); + ThreadContext.clearAll(); + } + + @AfterEach + void teardown() { + ThreadContext.clearAll(); + removeAndStopAppenders(); + reset(); + } + + void removeAndStopAppenders() { + final Map map = logger.getAppenders(); + for (final Map.Entry entry : map.entrySet()) { + final Appender appender = entry.getValue(); + logger.removeAppender(appender); + appender.stop(); + } + } + + static void reset() { + tcpServer.reset(); + udpServer.reset(); + } + + @Test + void testTcpAppender1() throws Exception { + testTcpAppender(tcpServer, logger, Constants.ENCODER_BYTE_BUFFER_SIZE); + } + + @Test + @Disabled("WIP Bug when this method runs after testTcpAppender1()") + void testTcpAppender2() throws Exception { + testTcpAppender(tcpServer, logger, Constants.ENCODER_BYTE_BUFFER_SIZE); + } + + static void testTcpAppender(final TcpSocketTestServer tcpTestServer, final Logger logger, final int bufferSize) + throws Exception { + // @formatter:off + final SocketAppender appender = SocketAppender.newBuilder() + .setHost("localhost") + .setPort(tcpTestServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setBufferSize(bufferSize) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) + .build(); + // @formatter:on + appender.start(); + assertEquals(bufferSize, appender.getManager().getByteBuffer().capacity()); + + // set appender on root and set level to debug + logger.addAppender(appender); + logger.setAdditive(false); + logger.setLevel(Level.DEBUG); + final String tcKey = "UUID"; + final String expectedUuidStr = UUID.randomUUID().toString(); + ThreadContext.put(tcKey, expectedUuidStr); + ThreadContext.push(expectedUuidStr); + final String expectedExMsg = "This is a test"; + try { + logger.debug("This is a test message"); + final Throwable child = new LoggingException(expectedExMsg); + logger.error("Throwing an exception", child); + logger.debug("This is another test message"); + } finally { + ThreadContext.remove(tcKey); + ThreadContext.pop(); + } + Thread.sleep(250); + LogEvent event = tcpTestServer.getQueue().poll(3, TimeUnit.SECONDS); + 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(event, "No event retrieved"); + assertEquals("Throwing an exception", event.getMessage().getFormattedMessage(), "Incorrect event"); + assertTrue(tcpTestServer.getCount() > 1, "Message not delivered via TCP"); + assertEquals(expectedUuidStr, event.getContextStack().pop()); + assertNotNull(event.getThrownProxy()); + assertEquals(expectedExMsg, event.getThrownProxy().getMessage()); + } + + @Test + void testDefaultProtocol() { + // @formatter:off + final SocketAppender appender = SocketAppender.newBuilder() + .setPort(tcpServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) + .build(); + // @formatter:on + assertNotNull(appender); + appender.stop(); + } + + @Test + void testUdpAppender() throws Exception { + try { + udpServer.latch.await(); + } catch (final InterruptedException ex) { + ex.printStackTrace(); + } + + // @formatter:off + final SocketAppender appender = SocketAppender.newBuilder() + .setProtocol(Protocol.UDP) + .setPort(tcpServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) + .build(); + // @formatter:on + appender.start(); + + // set appender on root and set level to debug + logger.addAppender(appender); + logger.setAdditive(false); + logger.setLevel(Level.DEBUG); + logger.debug("This is a udp message"); + final LogEvent event = udpServer.getQueue().poll(3, TimeUnit.SECONDS); + assertNotNull(event, "No event retrieved"); + assertEquals("This is a udp message", event.getMessage().getFormattedMessage(), "Incorrect event"); + assertTrue(udpServer.getCount() > 0, "Message not delivered via UDP"); + } + + @Test + void testTcpAppenderDeadlock() throws Exception { + + // @formatter:off + final SocketAppender appender = SocketAppender.newBuilder() + .setHost("localhost") + .setPort(DYN_PORT) + .setReconnectDelayMillis(100) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) + .build(); + // @formatter:on + appender.start(); + // set appender on root and set level to debug + logger.addAppender(appender); + logger.setAdditive(false); + logger.setLevel(Level.DEBUG); + + final TcpSocketTestServer tcpSocketServer = new TcpSocketTestServer(DYN_PORT); + try { + tcpSocketServer.start(); + + logger.debug("This message is written because a deadlock never."); + + final LogEvent event = tcpSocketServer.getQueue().poll(3, TimeUnit.SECONDS); + assertNotNull(event, "No event retrieved"); + } finally { + tcpSocketServer.shutdown(); + } + } + + @Test + void testTcpAppenderNoWait() { + // @formatter:off + final SocketAppender appender = SocketAppender.newBuilder() + .setHost("localhost") + .setPort(ERROR_PORT) + .setReconnectDelayMillis(100) + .setName("test") + .setImmediateFail(false) + .setIgnoreExceptions(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) + .build(); + // @formatter:on + appender.start(); + // set appender on root and set level to debug + logger.addAppender(appender); + 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(); + } + } + + public static class UdpSocketTestServer extends Thread { + + private final DatagramSocket sock; + private boolean shutdown = false; + private Thread thread; + private final CountDownLatch latch = new CountDownLatch(1); + private volatile int count = 0; + private final BlockingQueue queue; + private final ObjectMapper objectMapper = new Log4jJsonObjectMapper(); + + public UdpSocketTestServer() throws IOException { + this.sock = new DatagramSocket(PORT); + this.queue = new ArrayBlockingQueue<>(10); + } + + public void reset() { + queue.clear(); + count = 0; + } + + public void shutdown() { + this.shutdown = true; + thread.interrupt(); + try { + thread.join(100); + } catch (final InterruptedException ie) { + System.out.println("Unable to stop server"); + } + } + + @Override + public void run() { + this.thread = Thread.currentThread(); + final byte[] bytes = new byte[4096]; + final DatagramPacket packet = new DatagramPacket(bytes, bytes.length); + try { + while (!shutdown) { + latch.countDown(); + sock.receive(packet); + ++count; + final LogEvent event = objectMapper.readValue(packet.getData(), Log4jLogEvent.class); + queue.add(event); + } + } catch (final Throwable e) { + e.printStackTrace(); + if (!shutdown) { + Throwables.rethrow(e); + } + } + } + + public int getCount() { + return count; + } + + public BlockingQueue getQueue() { + return queue; + } + } + + public static class TcpSocketTestServer extends Thread { + + private final ServerSocket serverSocket; + private volatile boolean shutdown = false; + private volatile int count = 0; + private final BlockingQueue queue; + private final ObjectMapper objectMapper = new Log4jJsonObjectMapper(); + + @SuppressWarnings("resource") + public TcpSocketTestServer(final int port) throws IOException { + this(new ServerSocket(port)); + } + + public TcpSocketTestServer(final ServerSocket serverSocket) { + this.serverSocket = serverSocket; + this.queue = new ArrayBlockingQueue<>(10); + } + + public int getLocalPort() { + return serverSocket.getLocalPort(); + } + + public void reset() { + queue.clear(); + count = 0; + } + + public void shutdown() { + this.shutdown = true; + interrupt(); + try { + this.join(100); + } catch (final InterruptedException ie) { + System.out.println("Unable to stop server"); + } + } + + @Override + public void run() { + try { + try (final Socket socket = serverSocket.accept()) { + if (socket != null) { + final InputStream is = socket.getInputStream(); + while (!shutdown) { + final MappingIterator mappingIterator = + objectMapper.readerFor(Log4jLogEvent.class).readValues(is); + while (mappingIterator.hasNextValue()) { + queue.add(mappingIterator.nextValue()); + ++count; + } + } + } + } + } catch (final EOFException eof) { + // Socket is closed. + } catch (final Exception e) { + if (!shutdown) { + Throwables.rethrow(e); + } + } + } + + public BlockingQueue getQueue() { + return queue; + } + + public int getCount() { + return count; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.java new file mode 100644 index 00000000000..5d5b5a2dd9f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SslContexts.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.io.ByteArrayInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +final class SslContexts { + + static SSLContext createSslContext( + final String keyStoreType, + final String keyStoreLocation, + final char[] keyStorePassword, + final String trustStoreType, + final String trustStoreLocation, + final char[] trustStorePassword) + throws Exception { + + // Create the `KeyManagerFactory` + final KeyStore keyStore = loadKeyStore(keyStoreType, keyStoreLocation, keyStorePassword); + final String keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyAlgorithm); + keyManagerFactory.init(keyStore, keyStorePassword); + + // Create the `TrustManagerFactory` + final KeyStore trustStore = loadKeyStore(trustStoreType, trustStoreLocation, trustStorePassword); + final String trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustAlgorithm); + trustManagerFactory.init(trustStore); + + // Create the `SSLContext` + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return sslContext; + } + + private static KeyStore loadKeyStore(final String storeType, final String storeLocation, final char[] storePassword) + throws Exception { + final KeyStore keyStore = KeyStore.getInstance(storeType); + final ByteArrayInputStream storeByteStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(storeLocation))); + keyStore.load(storeByteStream, storePassword); + return keyStore; + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..2f91856bc13 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.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; + +import java.io.Serializable; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.SyslogAppender.Builder; +import org.apache.logging.log4j.core.layout.SyslogLayout; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; + +public class SyslogAppenderCustomLayoutTest extends SyslogAppenderTest { + + @Override + protected Facility getExpectedFacility() { + return Facility.LOCAL3; + } + + @Override + protected Builder newSyslogAppenderBuilder( + final Protocol protocol, final TlsSyslogMessageFormat format, final boolean newLine, final int port) { + final Layout layout = SyslogLayout.newBuilder() + .setFacility(Facility.LOCAL3) + .setIncludeNewLine(true) + .build(); + return super.newSyslogAppenderBuilder(protocol, format, newLine, port).setLayout(layout); + } +} 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..d0e28ed0c77 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.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.appender; + +import static org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat.LEGACY_BSD; +import static org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat.SYSLOG; + +import java.io.IOException; +import java.net.SocketException; +import org.apache.logging.log4j.core.appender.SyslogAppender.Builder; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public abstract class SyslogAppenderTest extends SyslogAppenderTestBase { + + public SyslogAppenderTest() { + root = ctx.getLogger("SyslogAppenderTest"); + } + + @BeforeEach + public void setUp() { + sentMessages.clear(); + } + + @AfterEach + public void teardown() { + removeAppenders(); + if (syslogServer != null) { + syslogServer.shutdown(); + } + } + + @Test + public void testTCPAppender() throws Exception { + initTCPTestEnvironment(LEGACY_BSD); + + sendAndCheckLegacyBsdMessage("This is a test message"); + sendAndCheckLegacyBsdMessage("This is a test message 2"); + } + + @Test + public void testDefaultAppender() throws Exception { + initTCPTestEnvironment(LEGACY_BSD); + + sendAndCheckLegacyBsdMessage("This is a test message"); + sendAndCheckLegacyBsdMessage("This is a test message 2"); + } + + @Test + public void testTCPStructuredAppender() throws Exception { + initTCPTestEnvironment(SYSLOG); + + sendAndCheckStructuredMessage(); + } + + @Test + public void testUDPAppender() throws Exception { + initUDPTestEnvironment(LEGACY_BSD); + + sendAndCheckLegacyBsdMessage("This is a test message"); + root.removeAppender(appender); + appender.stop(); + } + + @Test + public void testUDPStructuredAppender() throws Exception { + initUDPTestEnvironment(SYSLOG); + + sendAndCheckStructuredMessage(); + root.removeAppender(appender); + appender.stop(); + } + + protected void initUDPTestEnvironment(final TlsSyslogMessageFormat messageFormat) throws SocketException { + syslogServer = MockSyslogServerFactory.createUDPSyslogServer(); + syslogServer.start(); + initAppender(Protocol.UDP, messageFormat, syslogServer.getLocalPort()); + } + + protected void initTCPTestEnvironment(final TlsSyslogMessageFormat messageFormat) throws IOException { + syslogServer = MockSyslogServerFactory.createTCPSyslogServer(); + syslogServer.start(); + initAppender(Protocol.TCP, messageFormat, syslogServer.getLocalPort()); + } + + protected void initAppender(final Protocol protocol, final TlsSyslogMessageFormat messageFormat, final int port) { + appender = createAppender(protocol, messageFormat, port); + validate(appender); + appender.start(); + initRootLogger(appender); + } + + protected SyslogAppender createAppender( + final Protocol protocol, final TlsSyslogMessageFormat format, final int port) { + return newSyslogAppenderBuilder(protocol, format, includeNewLine, port).build(); + } + + protected Builder newSyslogAppenderBuilder( + final Protocol protocol, final TlsSyslogMessageFormat format, final boolean newLine, final int port) { + // @formatter:off + return SyslogAppender.newSyslogAppenderBuilder() + .setHost("localhost") + .setPort(port) + .setProtocol(protocol) + .setReconnectDelayMillis(-1) + .setImmediateFail(true) + .setName("Test") + .setImmediateFlush(true) + .setIgnoreExceptions(false) + .setFacility(Facility.LOCAL0) + .setId("Audit") + .setEnterpriseNumber("18060") + .setIncludeMdc(true) + .setMdcId("RequestContext") + .setNewLine(includeNewLine) + .setAppName("TestApp") + .setMsgId("Test") + .setIncludes("ipAddress,loginId") + .setFormat(format == SYSLOG ? SyslogAppender.RFC5424 : null) + .setAdvertise(false); + // @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 77% 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..5af62582bf2 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 @@ -1,27 +1,31 @@ /* * 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.logging.log4j.core.appender; +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.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; @@ -33,29 +37,27 @@ 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.test.junit.UsingStatusListener; import org.apache.logging.log4j.util.Strings; -import org.junit.Assert; -import org.junit.BeforeClass; - -import static org.junit.Assert.*; +import org.junit.jupiter.api.BeforeAll; -public abstract class SyslogAppenderTestBase { +@UsingStatusListener +abstract class SyslogAppenderTestBase { protected static final String line1 = - "TestApp - Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + - "[RequestContext@18060 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete"; + "TestApp - Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + + "[RequestContext@18060 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete"; protected LoggerContext ctx = LoggerContext.getContext(); protected static final int DEFAULT_TIMEOUT_IN_MS = 100; - protected static final int PORTNUM = 8199; protected MockSyslogServer syslogServer; protected SyslogAppender appender; protected Logger root = ctx.getLogger("SyslogAppenderTest"); protected List sentMessages = new ArrayList<>(); protected boolean includeNewLine = true; - @BeforeClass - public static void setupClass() throws Exception { + @BeforeAll + public static void setupClass() { LoggerContext.getContext().reconfigure(); } @@ -107,28 +109,32 @@ protected void sendInfoStructuredMessage() { } protected void checkTheNumberOfSentAndReceivedMessages() throws InterruptedException { - assertEquals("The number of received messages should be equal with the number of sent messages", - sentMessages.size(), getReceivedMessages(DEFAULT_TIMEOUT_IN_MS).size()); + assertEquals( + sentMessages.size(), + getReceivedMessages(DEFAULT_TIMEOUT_IN_MS).size(), + "The number of received messages should be equal with the number of sent messages"); } protected void checkTheEqualityOfSentAndReceivedMessages(final Level expectedLevel) throws InterruptedException { final List receivedMessages = getReceivedMessages(DEFAULT_TIMEOUT_IN_MS); - assertNotNull("No messages received", receivedMessages); + assertNotNull(receivedMessages, "No messages received"); for (int i = 0; i < receivedMessages.size(); i++) { final String receivedMessage = receivedMessages.get(i); final String sentMessage = sentMessages.get(i); final String suffix = includeNewLine ? "\n" : Strings.EMPTY; - assertTrue("Incorrect message received: " + receivedMessage, - receivedMessage.endsWith(sentMessage + suffix) || receivedMessage.contains(sentMessage)); + assertTrue( + receivedMessage.endsWith(sentMessage + suffix) || receivedMessage.contains(sentMessage), + "Incorrect message received: " + receivedMessage); final int expectedPriority = Priority.getPriority(getExpectedFacility(), expectedLevel); - assertTrue("Expected facility " + expectedPriority + " in message " + receivedMessage, - receivedMessage.startsWith("<" + expectedPriority + ">")); + assertTrue( + receivedMessage.startsWith("<" + expectedPriority + ">"), + "Expected facility " + expectedPriority + " in message " + receivedMessage); } } protected void removeAppenders() { - final Map map = root.getAppenders(); + final Map map = root.getAppenders(); for (final Map.Entry entry : map.entrySet()) { final Appender app = entry.getValue(); root.removeAppender(app); @@ -160,15 +166,15 @@ protected void validate(final SyslogAppender syslogAppender) { } else if (layout instanceof Rfc5424Layout) { validate((Rfc5424Layout) layout); } else { - Assert.fail("Unexpected layout: " + layout); + fail("Unexpected layout: " + layout); } } protected void validate(final Rfc5424Layout layout) { - Assert.assertEquals(getExpectedFacility(), layout.getFacility()); + assertEquals(getExpectedFacility(), layout.getFacility()); } protected void validate(final SyslogLayout layout) { - Assert.assertEquals(getExpectedFacility(), layout.getFacility()); + assertEquals(getExpectedFacility(), layout.getFacility()); } } 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..b18673e4be2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.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.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.appender.SyslogAppender.Builder; +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.SslKeyStoreConstants; +import org.apache.logging.log4j.core.net.ssl.StoreConfigurationException; +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.TlsSyslogMessageFormat; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogTestUtil; +import org.junit.jupiter.api.Test; + +class TlsSyslogAppenderTest extends SyslogAppenderTest { + + private SSLServerSocketFactory serverSocketFactory; + private SslConfiguration sslConfiguration; + + public TlsSyslogAppenderTest() throws StoreConfigurationException { + initServerSocketFactory(); + root = ctx.getLogger("TLSSyslogAppenderTest"); + } + + @Test + 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 + 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 + void sendStructuredMessageOverTls() throws InterruptedException, IOException { + initTlsTestEnvironment(1, TlsSyslogMessageFormat.SYSLOG); + + sendAndCheckStructuredMessage(); + } + + @Test + 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( + SslKeyStoreConstants.KEYSTORE_LOCATION, SslKeyStoreConstants::KEYSTORE_PWD, null, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants::TRUSTSTORE_PWD, null, null); + sslConfiguration = SslConfiguration.createSSLConfiguration(null, ksc, tsc); + serverSocketFactory = sslConfiguration.getSslContext().getServerSocketFactory(); + } + + private void initTlsTestEnvironment(final int numberOfMessages, final TlsSyslogMessageFormat messageFormat) + throws IOException { + final SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(0); + + syslogServer = MockSyslogServerFactory.createTLSSyslogServer(numberOfMessages, messageFormat, sslServerSocket); + syslogServer.start(); + initAppender(Protocol.SSL, messageFormat, syslogServer.getLocalPort()); + } + + @Override + protected Builder newSyslogAppenderBuilder( + final Protocol protocol, final TlsSyslogMessageFormat format, final boolean newLine, final int port) { + return super.newSyslogAppenderBuilder(protocol, format, newLine, port) + .setSslConfiguration(protocol == Protocol.SSL ? sslConfiguration : null); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..653718d0bf0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.util.Chars; +import org.junit.jupiter.api.Test; + +class TlsSyslogFrameTest { + private static final String TEST_MESSAGE = "The quick brown fox jumps over the lazy dog"; + + @Test + void equals() { + final TlsSyslogFrame first = new TlsSyslogFrame(TEST_MESSAGE); + final TlsSyslogFrame second = new TlsSyslogFrame(TEST_MESSAGE); + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); + } + + @Test + void notEquals() { + final TlsSyslogFrame first = new TlsSyslogFrame("A message"); + final TlsSyslogFrame second = new TlsSyslogFrame("B message"); + assertNotEquals(first, second); + assertNotEquals(first.hashCode(), second.hashCode()); + } + + @Test + 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; + assertEquals(expected, frame.toString()); + } +} 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..37e715216a1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java @@ -0,0 +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.appender; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +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; + +/** + * Tests {@link WriterAppender}. + */ +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 + void testWriterAppenderToCharArrayWriter() { + test(new CharArrayWriter()); + } + + @Test + void testWriterAppenderToOutputStreamWriter() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final Writer writer = new OutputStreamWriter(out); + test(out, writer); + } + + @Test + void testWriterAppenderToPrintWriter() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final Writer writer = new PrintWriter(out); + test(out, writer); + } + + @Test + void testWriterAppenderToStringWriter() { + test(new StringWriter()); + } + + @Test + void testBuilder() { + // This should compile + WriterAppender.newBuilder() + .setTarget(new StringWriter()) + .setName("testWriterAppender") + .build(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java new file mode 100644 index 00000000000..731ee7ffa8d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.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.appender; + +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.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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests a "compact" XML file, no extra spaces or end of lines. + */ +@Tag("Layouts.Xml") +class XmlCompactFileAppenderTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "XmlCompactFileAppenderTest.xml"); + } + + @Test + void testFlushAtEndOfBatch() throws Exception { + final File file = new File("target", "XmlCompactFileAppenderTest.log"); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String logMsg = "Message flushed with immediate flush=false"; + log.info(logMsg); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + String line1; + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + line1 = reader.readLine(); + } finally { + file.delete(); + } + assertNotNull(line1, "line1"); + final String msg1 = ""; + assertTrue(line1.contains(msg1), "line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + ']'); + + final String msg2 = ""; + assertTrue(line1.contains(msg2), "line1 incorrect: [" + line1 + "], does not contain: [" + msg2 + ']'); + + final String msg3 = " contextSelector) { + this.loggerContextRule = new LoggerContextRule("XmlCompleteFileAppenderTest.xml", contextSelector); + this.cleanFiles = new CleanFiles(logFile); + this.ruleChain = RuleChain.outerRule(cleanFiles).around(loggerContextRule); + } + + @Parameters(name = "{0}") + public static Class[] getParameters() { + return CoreContextSelectors.CLASSES; + } + + private final File logFile = new File("target", "XmlCompleteFileAppenderTest.log"); + private final LoggerContextRule loggerContextRule; + private final CleanFiles cleanFiles; + + @Rule + public RuleChain ruleChain; + + @Test + public void testFlushAtEndOfBatch() throws Exception { + final Logger logger = this.loggerContextRule.getLogger("com.foo.Bar"); + final String logMsg = "Message flushed with immediate flush=false"; + logger.info(logMsg); + CoreLoggerContexts.stopLoggerContext(false, logFile); // stop async thread + + String line1; + String line2; + String line3; + String line4; + String line5; + try (final BufferedReader reader = new BufferedReader(new FileReader(logFile))) { + line1 = reader.readLine(); + line2 = reader.readLine(); + reader.readLine(); // ignore the empty line after the root + line3 = reader.readLine(); + line4 = reader.readLine(); + line5 = reader.readLine(); + } finally { + logFile.delete(); + } + assertNotNull("line1", line1); + final String msg1 = ""; + assertEquals("line1 incorrect: [" + line1 + "], does not contain: [" + msg1 + ']', msg1, line1); + + assertNotNull("line2", line2); + final String msg2 = ""; + assertEquals("line2 incorrect: [" + line2 + "], does not contain: [" + msg2 + ']', msg2, line2); + + assertNotNull("line3", line3); + final String msg3 = "Expected Events XML is as below.

+ *
+     * <?xml version="1.0" encoding="UTF-8"?>
+     * <Events xmlns="http://logging.apache.org/log4j/2.0/events">
+     *
+     * <Event xmlns="http://logging.apache.org/log4j/2.0/events" thread="main" level="INFO" loggerName="com.foo.Bar" endOfBatch="true" loggerFqcn="org.apache.logging.log4j.spi.AbstractLogger" threadId="12" threadPriority="5">
+     * <Instant epochSecond="1515889414" nanoOfSecond="144000000" epochMillisecond="1515889414144" nanoOfMillisecond="0"/>
+     * <Message>First Msg tag must be in level 2 after correct indentation</Message>
+     * </Event>
+     *
+     * <Event xmlns="http://logging.apache.org/log4j/2.0/events" thread="main" level="INFO" loggerName="com.foo.Bar" endOfBatch="true" loggerFqcn="org.apache.logging.log4j.spi.AbstractLogger" threadId="12" threadPriority="5">
+     * <Instant epochSecond="1515889414" nanoOfSecond="144000000" epochMillisecond="1515889414144" nanoOfMillisecond="0"/>
+     * <Message>Second Msg tag must also be in level 2 after correct indentation</Message>
+     * </Event>
+     * </Events>
+     * 
+ * @throws Exception + */ + @Test + public void testChildElementsAreCorrectlyIndented() throws Exception { + final Logger logger = this.loggerContextRule.getLogger("com.foo.Bar"); + final String firstLogMsg = "First Msg tag must be in level 2 after correct indentation"; + logger.info(firstLogMsg); + final String secondLogMsg = "Second Msg tag must also be in level 2 after correct indentation"; + logger.info(secondLogMsg); + CoreLoggerContexts.stopLoggerContext(false, logFile); // stop async thread + + final int[] indentations = { + 0, // "\n" + 0, // "\n" + -1, // empty + 2, // " \n" + 4, // " \n" + 4, // " First Msg tag must be in level 2 after correct indentation\n" + + 2, // " \n" + -1, // empty + 2, // " \n" + + 4, // " \n" + + 4, // " Second Msg tag must also be in level 2 after correct indentation\n" + + 2, // " \n" + + 0, // "\n"; + }; + final List lines1 = Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8); + + assertEquals("number of lines", indentations.length, lines1.size()); + for (int i = 0; i < indentations.length; i++) { + final String line = lines1.get(i); + if (line.trim().isEmpty()) { + assertEquals(-1, indentations[i]); + } else { + final String padding = " ".substring(0, indentations[i]); + assertTrue( + "Expected " + indentations[i] + " leading spaces but got: " + line, line.startsWith(padding)); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java new file mode 100644 index 00000000000..ad186d21129 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.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; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests a "complete" XML file a.k.a. a well-formed XML file. + */ +@Tag("Layouts.Xml") +class XmlFileAppenderTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "XmlFileAppenderTest.xml"); + } + + @Test + void testFlushAtEndOfBatch() throws Exception { + final File file = new File("target", "XmlFileAppenderTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String logMsg = "Message flushed with immediate flush=false"; + log.info(logMsg); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + final List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); + file.delete(); + + final String[] expect = { + "", // ? unsure why initial empty line... + "", // + }; + + for (int i = 0; i < expect.length; i++) { + assertTrue( + lines.get(i).contains(expect[i]), + "Expected line " + i + " to contain " + expect[i] + " but got: " + lines.get(i)); + } + + final String location = "testFlushAtEndOfBatch"; + assertFalse(lines.get(0).contains(location), "no location"); + } +} 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 76% 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..fdf2df70843 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db; @@ -26,19 +26,36 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; -import java.io.Serializable; import java.util.concurrent.TimeUnit; - 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 abstract static class LocalAbstractDatabaseManager extends AbstractDatabaseManager { + public LocalAbstractDatabaseManager(final String name, final int bufferSize) { + super(name, bufferSize); + } + } + private LocalAbstractDatabaseAppender appender; + @Mock private LocalAbstractDatabaseManager manager; @@ -46,6 +63,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), isNull()); + reset(manager); + + appender.append(event2); + then(manager).should().isBuffered(); + then(manager).should().writeThrough(same(event2), isNull()); + reset(manager); + } + @Test public void testNameAndGetLayout01() { setUp("testName01"); @@ -62,17 +98,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 +115,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-test/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 new file mode 100644 index 00000000000..38fad66231c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java @@ -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. + */ +package org.apache.logging.log4j.core.appender.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.BDDMockito.then; +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; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.Test; + +class AbstractDatabaseManagerTest { + // this stub is provided because mocking constructors is hard + private static class StubDatabaseManager extends AbstractDatabaseManager { + + protected StubDatabaseManager(final String name, final int bufferSize) { + super(name, bufferSize); + } + + @Override + protected boolean commitAndClose() { + return true; + } + + @Override + protected void connectAndStart() { + // noop + } + + @Override + protected boolean shutdownInternal() { + return true; + } + + @Override + protected void startupInternal() { + // noop + } + + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + // noop + } + } + + private AbstractDatabaseManager manager; + + public void setUp(final String name, final int buffer) { + manager = spy(new StubDatabaseManager(name, buffer)); + } + + @Test + void testBuffering01() throws Exception { + setUp("name", 0); + + final LogEvent event1 = mock(LogEvent.class); + final LogEvent event2 = mock(LogEvent.class); + final LogEvent event3 = mock(LogEvent.class); + + manager.startup(); + then(manager).should().startupInternal(); + reset(manager); + + manager.write(event1, null); + then(manager).should().writeThrough(same(event1), isNull()); + then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); + then(manager).should().writeInternal(same(event1), isNull()); + then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); + reset(manager); + + manager.write(event2, null); + then(manager).should().writeThrough(same(event2), isNull()); + then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); + then(manager).should().writeInternal(same(event2), isNull()); + then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); + reset(manager); + + manager.write(event3, null); + then(manager).should().writeThrough(same(event3), isNull()); + then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); + then(manager).should().writeInternal(same(event3), isNull()); + then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); + reset(manager); + } + + @Test + void testBuffering02() throws Exception { + setUp("name", 4); + + final LogEvent event1 = mock(LogEvent.class); + final LogEvent event2 = mock(LogEvent.class); + final LogEvent event3 = mock(LogEvent.class); + final LogEvent event4 = mock(LogEvent.class); + + final LogEvent event1copy = mock(LogEvent.class); + final LogEvent event2copy = mock(LogEvent.class); + final LogEvent event3copy = mock(LogEvent.class); + final LogEvent event4copy = mock(LogEvent.class); + + when(event1.toImmutable()).thenReturn(event1copy); + when(event2.toImmutable()).thenReturn(event2copy); + when(event3.toImmutable()).thenReturn(event3copy); + when(event4.toImmutable()).thenReturn(event4copy); + + manager.startup(); + then(manager).should().startupInternal(); + + manager.write(event1, null); + manager.write(event2, null); + manager.write(event3, null); + manager.write(event4, null); + + then(manager).should().connectAndStart(); + verify(manager, times(5)).isBuffered(); // 4 + 1 in flush() + then(manager).should().writeInternal(same(event1copy), isNull()); + then(manager).should().buffer(event1); + then(manager).should().writeInternal(same(event2copy), isNull()); + then(manager).should().buffer(event2); + then(manager).should().writeInternal(same(event3copy), isNull()); + then(manager).should().buffer(event3); + then(manager).should().writeInternal(same(event4copy), isNull()); + then(manager).should().buffer(event4); + then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); + } + + @Test + void testBuffering03() throws Exception { + setUp("name", 10); + + final LogEvent event1 = mock(LogEvent.class); + final LogEvent event2 = mock(LogEvent.class); + final LogEvent event3 = mock(LogEvent.class); + + final LogEvent event1copy = mock(LogEvent.class); + final LogEvent event2copy = mock(LogEvent.class); + final LogEvent event3copy = mock(LogEvent.class); + + when(event1.toImmutable()).thenReturn(event1copy); + when(event2.toImmutable()).thenReturn(event2copy); + when(event3.toImmutable()).thenReturn(event3copy); + + manager.startup(); + then(manager).should().startupInternal(); + + manager.write(event1, null); + manager.write(event2, null); + manager.write(event3, null); + manager.flush(); + + then(manager).should().connectAndStart(); + verify(manager, times(4)).isBuffered(); + then(manager).should().writeInternal(same(event1copy), isNull()); + then(manager).should().buffer(event1); + then(manager).should().writeInternal(same(event2copy), isNull()); + then(manager).should().buffer(event2); + then(manager).should().writeInternal(same(event3copy), isNull()); + then(manager).should().buffer(event3); + then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); + } + + @Test + void testBuffering04() throws Exception { + setUp("name", 10); + + final LogEvent event1 = mock(LogEvent.class); + final LogEvent event2 = mock(LogEvent.class); + final LogEvent event3 = mock(LogEvent.class); + + final LogEvent event1copy = mock(LogEvent.class); + final LogEvent event2copy = mock(LogEvent.class); + final LogEvent event3copy = mock(LogEvent.class); + + when(event1.toImmutable()).thenReturn(event1copy); + when(event2.toImmutable()).thenReturn(event2copy); + when(event3.toImmutable()).thenReturn(event3copy); + + manager.startup(); + then(manager).should().startupInternal(); + + manager.write(event1, null); + manager.write(event2, null); + manager.write(event3, null); + manager.shutdown(); + + then(manager).should().connectAndStart(); + verify(manager, times(4)).isBuffered(); + then(manager).should().writeInternal(same(event1copy), isNull()); + then(manager).should().buffer(event1); + then(manager).should().writeInternal(same(event2copy), isNull()); + then(manager).should().buffer(event2); + then(manager).should().writeInternal(same(event3copy), isNull()); + then(manager).should().buffer(event3); + then(manager).should().commitAndClose(); + then(manager).should().shutdownInternal(); + then(manager).shouldHaveNoMoreInteractions(); + } + + @Test + void testStartupShutdown01() throws Exception { + setUp("testName01", 0); + + assertEquals("testName01", manager.getName(), "The name is not correct."); + assertFalse(manager.isRunning(), "The manager should not have started."); + + manager.startup(); + then(manager).should().startupInternal(); + assertTrue(manager.isRunning(), "The manager should be running now."); + + manager.shutdown(); + then(manager).should().shutdownInternal(); + assertFalse(manager.isRunning(), "The manager should not be running anymore."); + } + + @Test + void testStartupShutdown02() throws Exception { + setUp("anotherName02", 0); + + assertEquals("anotherName02", manager.getName(), "The name is not correct."); + assertFalse(manager.isRunning(), "The manager should not have started."); + + manager.startup(); + then(manager).should().startupInternal(); + assertTrue(manager.isRunning(), "The manager should be running now."); + + manager.releaseSub(-1, null); + then(manager).should().shutdownInternal(); + assertFalse(manager.isRunning(), "The manager should not be running anymore."); + } + + @Test + void testToString01() { + setUp("someName01", 0); + + assertEquals("someName01", manager.toString(), "The string is not correct."); + } + + @Test + void testToString02() { + setUp("bufferSize=12, anotherKey02=coolValue02", 12); + + assertEquals("bufferSize=12, anotherKey02=coolValue02", manager.toString(), "The string is not correct."); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractH2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractH2Test.java new file mode 100644 index 00000000000..bd1990746ef --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractH2Test.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.appender.db.jdbc; + +import java.io.IOException; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Abstracts H2 test clean up. + */ +public abstract class AbstractH2Test { + + @AfterClass + @BeforeClass + public static void classDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderDataSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderDataSourceTest.java new file mode 100644 index 00000000000..a2d904871fa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderDataSourceTest.java @@ -0,0 +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.appender.db.jdbc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import javax.sql.DataSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Throwables; +import org.h2.util.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * Abstract unit test for JdbcAppender using a {@link DataSource} configuration. + */ +public abstract class AbstractJdbcAppenderDataSourceTest extends AbstractJdbcDataSourceTest { + + @Rule + public final RuleChain rules; + + private final JdbcRule jdbcRule; + + protected AbstractJdbcAppenderDataSourceTest(final JdbcRule jdbcRule) { + this.rules = RuleChain.emptyRuleChain() + .around(new JndiRule("java:/comp/env/jdbc/TestDataSourceAppender", createMockDataSource())) + .around(jdbcRule) + .around(new LoggerContextRule("org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source.xml")); + this.jdbcRule = jdbcRule; + } + + private DataSource createMockDataSource() { + try { + final DataSource dataSource = mock(DataSource.class); + given(dataSource.getConnection()) + .willAnswer(invocation -> jdbcRule.getConnectionSource().getConnection()); + return dataSource; + } catch (final SQLException e) { + Throwables.rethrow(e); + throw new InternalError("unreachable"); + } + } + + @Test + public void testDataSourceConfig() throws Exception { + try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) { + final Error exception = new Error("Final error massage is fatal!"); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (final PrintWriter writer = new PrintWriter(outputStream)) { + exception.printStackTrace(writer); + } + final String stackTrace = outputStream.toString(); + + final long millis = System.currentTimeMillis(); + + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig"); + logger.trace("Data source logged message 01."); + logger.fatal("Error from data source 02.", exception); + + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = statement.executeQuery("SELECT * FROM dsLogEntry ORDER BY id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + final long date = resultSet.getTimestamp("eventDate").getTime(); + assertTrue("The date should be later than pre-logging (1).", date >= millis); + assertTrue("The date should be earlier than now (1).", date <= System.currentTimeMillis()); + assertEquals( + "The literal column is not correct (1).", + "Literal Value of Data Source", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (1).", "FATAL", resultSet.getNString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getNString("logger")); + assertEquals( + "The message column is not correct (1).", + "Error from data source 02.", + resultSet.getString("message")); + assertEquals( + "The exception column is not correct (1).", + stackTrace, + IOUtils.readStringAndClose( + resultSet.getNClob("exception").getCharacterStream(), -1)); + + assertFalse("There should not be two rows.", resultSet.next()); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderFactoryMethodTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderFactoryMethodTest.java new file mode 100644 index 00000000000..4c1c29b0a21 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcAppenderFactoryMethodTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.commons.lang3.StringUtils; +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.JdbcRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.util.Strings; +import org.h2.util.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * Abstract unit test for JdbcAppender using a {@link FactoryMethodConnectionSource} configuration. + */ +public abstract class AbstractJdbcAppenderFactoryMethodTest { + + @Rule + public final RuleChain rules; + + private final JdbcRule jdbcRule; + + protected AbstractJdbcAppenderFactoryMethodTest(final JdbcRule jdbcRule, final String databaseType) { + this.rules = RuleChain.emptyRuleChain() + .around(jdbcRule) + .around(new LoggerContextRule("org/apache/logging/log4j/core/appender/db/jdbc/log4j2-" + databaseType + + "-factory-method.xml")); + this.jdbcRule = jdbcRule; + } + + @Test + public void testFactoryMethodConfig() throws Exception { + try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) { + final SQLException exception = new SQLException("Some other error message!"); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (final PrintWriter writer = new PrintWriter(outputStream)) { + exception.printStackTrace(writer); + } + final String stackTrace = outputStream.toString(); + + final long millis = System.currentTimeMillis(); + + ThreadContext.put("some_int", "42"); + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testFactoryMethodConfig"); + logger.debug("Factory logged message 01."); + logger.error("Error from factory 02.", exception); + + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = statement.executeQuery("SELECT * FROM fmLogEntry ORDER BY id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + long date = resultSet.getTimestamp("eventDate").getTime(); + long anotherDate = resultSet.getTimestamp("anotherDate").getTime(); + assertEquals(date, anotherDate); + assertTrue("The date should be later than pre-logging (1).", date >= millis); + assertTrue("The date should be earlier than now (1).", date <= System.currentTimeMillis()); + assertEquals( + "The literal column is not correct (1).", + "Some Other Literal Value", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (1).", "DEBUG", resultSet.getNString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getNString("logger")); + assertEquals( + "The message column is not correct (1).", + "Factory logged message 01.", + resultSet.getString("message")); + assertEquals( + "The exception column is not correct (1).", + Strings.EMPTY, + IOUtils.readStringAndClose( + resultSet.getNClob("exception").getCharacterStream(), -1)); + + assertTrue("There should be two rows.", resultSet.next()); + + date = resultSet.getTimestamp("eventDate").getTime(); + anotherDate = resultSet.getTimestamp("anotherDate").getTime(); + assertEquals(date, anotherDate); + assertTrue("The date should be later than pre-logging (2).", date >= millis); + assertTrue("The date should be earlier than now (2).", date <= System.currentTimeMillis()); + assertEquals( + "The literal column is not correct (2).", + "Some Other Literal Value", + resultSet.getString("literalColumn")); + assertEquals("The level column is not correct (2).", "ERROR", resultSet.getNString("level")); + assertEquals("The logger column is not correct (2).", logger.getName(), resultSet.getNString("logger")); + assertEquals( + "The message column is not correct (2).", + "Error from factory 02.", + resultSet.getString("message")); + assertEquals( + "The exception column is not correct (2).", + stackTrace, + IOUtils.readStringAndClose( + resultSet.getNClob("exception").getCharacterStream(), -1)); + + assertFalse("There should not be three rows.", resultSet.next()); + } + } + } + + @Test + public void testTruncate() { + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testFactoryMethodConfig"); + // Some drivers and database will not allow more data than the column defines. + // We really need a MySQL databases with a default configuration to test this. + logger.debug(StringUtils.repeat('A', 1000)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcDataSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcDataSourceTest.java new file mode 100644 index 00000000000..4167bdc678c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractJdbcDataSourceTest.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.db.jdbc; + +import javax.sql.DataSource; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Abstract unit test for JDBC using a {@link DataSource} configuration. + */ +public abstract class AbstractJdbcDataSourceTest { + + @AfterClass + public static void afterClass() { + System.clearProperty("log4j2.enableJndiJdbc"); + } + + @BeforeClass + public static void beforeClass() { + System.setProperty("log4j2.enableJndiJdbc", "true"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java new file mode 100644 index 00000000000..a27e5ce9adb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfigTest.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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 org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +class ColumnConfigTest { + + @Test + void testNullNameNoConfig() { + final ColumnConfig config = ColumnConfig.newBuilder().setPattern("%l").build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testPatternAndLiteralNoConfig() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setPattern("%l") + .setLiteral("literal") + .build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testPatternAndDateNoConfig() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setPattern("%l") + .setEventTimestamp(true) + .build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testLiteralAndDateNoConfig() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setLiteral("literal") + .setEventTimestamp(true) + .build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testNoSettingNoConfig01() { + final ColumnConfig config = ColumnConfig.newBuilder().setName("col").build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testNoSettingNoConfig02() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setEventTimestamp(false) + .build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testNoSettingNoConfig03() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setPattern(Strings.EMPTY) + .setLiteral(Strings.EMPTY) + .setEventTimestamp(false) + .build(); + + assertNull(config, "The result should be null."); + } + + @Test + void testDateColumn01() { + final ColumnConfig config = + ColumnConfig.newBuilder().setName("col").setEventTimestamp(true).build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col", config.getColumnName(), "The column name is not correct."); + assertNull(config.getLayout(), "The pattern should be null."); + assertNull(config.getLiteralValue(), "The literal value should be null."); + assertTrue(config.isEventTimestamp(), "The timestamp flag should be true."); + assertFalse(config.isUnicode(), "The unicode flag should be false."); + assertFalse(config.isClob(), "The clob flag should be false."); + } + + @Test + void testDateColumn02() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col2") + .setEventTimestamp(true) + .setUnicode(true) + .setClob(true) + .build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col2", config.getColumnName(), "The column name is not correct."); + assertNull(config.getLayout(), "The pattern should be null."); + assertNull(config.getLiteralValue(), "The literal value should be null."); + assertTrue(config.isEventTimestamp(), "The timestamp flag should be true."); + assertFalse(config.isUnicode(), "The unicode flag should be false."); + assertFalse(config.isClob(), "The clob flag should be false."); + } + + @Test + void testPatternColumn01() { + final ColumnConfig config = + ColumnConfig.newBuilder().setName("col").setPattern("%l").build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col", config.getColumnName(), "The column name is not correct."); + assertNotNull(config.getLayout(), "The pattern should not be null."); + assertEquals("%l", config.getLayout().toString(), "The pattern is not correct."); + assertNull(config.getLiteralValue(), "The literal value should be null."); + assertFalse(config.isEventTimestamp(), "The timestamp flag should be false."); + assertTrue(config.isUnicode(), "The unicode flag should be true."); + assertFalse(config.isClob(), "The clob flag should be false."); + } + + @Test + void testPatternColumn02() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col2") + .setPattern("%X{id} %level") + .setLiteral(Strings.EMPTY) + .setEventTimestamp(false) + .setUnicode(false) + .setClob(true) + .build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col2", config.getColumnName(), "The column name is not correct."); + assertNotNull(config.getLayout(), "The pattern should not be null."); + assertEquals("%X{id} %level", config.getLayout().toString(), "The pattern is not correct."); + assertNull(config.getLiteralValue(), "The literal value should be null."); + assertFalse(config.isEventTimestamp(), "The timestamp flag should be false."); + assertFalse(config.isUnicode(), "The unicode flag should be false."); + assertTrue(config.isClob(), "The clob flag should be true."); + } + + @Test + void testPatternColumn03() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col3") + .setPattern("%X{id} %level") + .setLiteral(Strings.EMPTY) + .setEventTimestamp(false) + .setUnicode(true) + .setClob(false) + .build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col3", config.getColumnName(), "The column name is not correct."); + assertNotNull(config.getLayout(), "The pattern should not be null."); + assertEquals("%X{id} %level", config.getLayout().toString(), "The pattern is not correct."); + assertNull(config.getLiteralValue(), "The literal value should be null."); + assertFalse(config.isEventTimestamp(), "The timestamp flag should be false."); + assertTrue(config.isUnicode(), "The unicode flag should be true."); + assertFalse(config.isClob(), "The clob flag should be false."); + } + + @Test + void testLiteralColumn01() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col") + .setLiteral("literalValue01") + .build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col", config.getColumnName(), "The column name is not correct."); + assertNull(config.getLayout(), "The pattern should be null."); + assertNotNull(config.getLiteralValue(), "The literal value should be null."); + assertEquals("literalValue01", config.getLiteralValue(), "The literal value is not correct."); + assertFalse(config.isEventTimestamp(), "The timestamp flag should be false."); + assertFalse(config.isUnicode(), "The unicode flag should be false."); + assertFalse(config.isClob(), "The clob flag should be false."); + } + + @Test + void testLiteralColumn02() { + final ColumnConfig config = ColumnConfig.newBuilder() + .setName("col2") + .setLiteral("USER1.MY_SEQUENCE.NEXT") + .setUnicode(true) + .setClob(true) + .build(); + + assertNotNull(config, "The result should not be null."); + assertEquals("col2", config.getColumnName(), "The column name is not correct."); + assertNull(config.getLayout(), "The pattern should be null."); + assertNotNull(config.getLiteralValue(), "The literal value should be null."); + assertEquals("USER1.MY_SEQUENCE.NEXT", config.getLiteralValue(), "The literal value is not correct."); + assertFalse(config.isEventTimestamp(), "The timestamp flag should be false."); + assertFalse(config.isUnicode(), "The unicode flag should be false."); + assertFalse(config.isClob(), "The clob flag should be false."); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java index a49404a83fc..d90d45ee0af 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSourceTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db.jdbc; @@ -25,11 +25,9 @@ import java.sql.Connection; import java.sql.SQLException; - import javax.sql.DataSource; - -import org.apache.logging.log4j.junit.JndiRule; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -37,26 +35,23 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class DataSourceConnectionSourceTest { +public class DataSourceConnectionSourceTest extends AbstractJdbcDataSourceTest { @Parameterized.Parameters(name = "{0}") public static Object[][] data() { - return new Object[][]{ - {"java:/comp/env/jdbc/Logging01"}, - {"java:/comp/env/jdbc/Logging02"} - }; + return new Object[][] {{"java:/comp/env/jdbc/Logging01"}, {"java:/comp/env/jdbc/Logging02"}}; } private static final String CONFIG = "log4j-fatalOnly.xml"; @Rule public final RuleChain rules; + private final DataSource dataSource = mock(DataSource.class); private final String jndiURL; public DataSourceConnectionSourceTest(final String jndiURL) { - this.rules = RuleChain.outerRule(new JndiRule(jndiURL, dataSource)) - .around(new LoggerContextRule(CONFIG)); + this.rules = RuleChain.outerRule(new JndiRule(jndiURL, dataSource)).around(new LoggerContextRule(CONFIG)); this.jndiURL = jndiURL; } @@ -91,8 +86,10 @@ public void testDataSource() throws SQLException { DataSourceConnectionSource source = DataSourceConnectionSource.createConnectionSource(jndiURL); assertNotNull("The connection source should not be null.", source); - assertEquals("The toString value is not correct.", - "dataSource{ name=" + jndiURL + ", value=" + dataSource + " }", source.toString()); + assertEquals( + "The toString value is not correct.", + "dataSource{ name=" + jndiURL + ", value=" + dataSource + " }", + source.toString()); assertSame("The connection is not correct (1).", connection1, source.getConnection()); assertSame("The connection is not correct (2).", connection2, source.getConnection()); @@ -101,5 +98,4 @@ public void testDataSource() throws SQLException { assertNull("The connection source should be null now.", source); } } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerH2ConnectionSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerH2ConnectionSourceTest.java new file mode 100644 index 00000000000..57096af936e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerH2ConnectionSourceTest.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.db.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.junit.Assert; +import org.junit.Test; + +public class DriverManagerH2ConnectionSourceTest extends AbstractH2Test { + + @Test + public void testH2Properties() throws SQLException { + final Property[] properties = new Property[] { + // @formatter:off + Property.createProperty("username", JdbcH2TestHelper.USER_NAME), + Property.createProperty("password", JdbcH2TestHelper.PASSWORD), + // @formatter:on + }; + // @formatter:off + final DriverManagerConnectionSource source = DriverManagerConnectionSource.newBuilder() + .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_IN_MEMORY) + .setProperties(properties) + .build(); + // @formatter:on + try (final Connection conn = source.getConnection()) { + Assert.assertFalse(conn.isClosed()); + } + } + + @Test + public void testH2UserAndPassword() throws SQLException { + // @formatter:off + final DriverManagerConnectionSource source = DriverManagerConnectionSource.newBuilder() + .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_IN_MEMORY) + .setUserName(JdbcH2TestHelper.USER_NAME.toCharArray()) + .setPassword(JdbcH2TestHelper.PASSWORD.toCharArray()) + .build(); + // @formatter:on + try (final Connection conn = source.getConnection()) { + Assert.assertFalse(conn.isClosed()); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.java new file mode 100644 index 00000000000..b9bcf18d59a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSourceTest.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.appender.db.jdbc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; + +public class FactoryMethodConnectionSourceTest { + private static final ThreadLocal holder = new ThreadLocal<>(); + private static final String CONFIG = "log4j-fatalOnly.xml"; + + @ClassRule + public static LoggerContextRule ctx = new LoggerContextRule(CONFIG); + + @After + public void tearDown() { + holder.remove(); + } + + @Test + public void testNoClassName() { + final FactoryMethodConnectionSource source = + FactoryMethodConnectionSource.createConnectionSource(null, "method"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testNoMethodName() { + final FactoryMethodConnectionSource source = + FactoryMethodConnectionSource.createConnectionSource("someClass", null); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadClassName() { + final FactoryMethodConnectionSource source = + FactoryMethodConnectionSource.createConnectionSource("org.apache.BadClass", "factoryMethod"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadMethodName() { + final FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + this.getClass().getName(), "factoryMethod"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testBadReturnType() { + final FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + BadReturnTypeFactory.class.getName(), "factoryMethod01"); + + assertNull("The connection source should be null.", source); + } + + @Test + public void testDataSourceReturnType() throws SQLException { + final DataSource dataSource = mock(DataSource.class); + try (final Connection connection1 = mock(Connection.class); + final Connection connection2 = mock(Connection.class)) { + + given(dataSource.getConnection()).willReturn(connection1, connection2); + + holder.set(dataSource); + + final FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + DataSourceFactory.class.getName(), "factoryMethod02"); + + assertNotNull("The connection source should not be null.", source); + assertEquals( + "The toString value is not correct.", + "factory{ public static javax.sql.DataSource[" + dataSource + "] " + + DataSourceFactory.class.getName() + ".factoryMethod02() }", + source.toString()); + assertSame("The connection is not correct (1).", connection1, source.getConnection()); + assertSame("The connection is not correct (2).", connection2, source.getConnection()); + } + } + + @Test + public void testConnectionReturnType() throws SQLException { + try (final Connection connection = mock(Connection.class)) { + + holder.set(connection); + + final FactoryMethodConnectionSource source = FactoryMethodConnectionSource.createConnectionSource( + ConnectionFactory.class.getName(), "anotherMethod03"); + + assertNotNull("The connection source should not be null.", source); + assertEquals( + "The toString value is not correct.", + "factory{ public static java.sql.Connection " + ConnectionFactory.class.getName() + + ".anotherMethod03() }", + source.toString()); + assertSame("The connection is not correct (1).", connection, source.getConnection()); + assertSame("The connection is not correct (2).", connection, source.getConnection()); + } + } + + @SuppressWarnings("unused") + protected static final class BadReturnTypeFactory { + public static String factoryMethod01() { + return "hello"; + } + } + + @SuppressWarnings("unused") + protected static final class DataSourceFactory { + public static DataSource factoryMethod02() { + return (DataSource) holder.get(); + } + } + + @SuppressWarnings("unused") + protected static final class ConnectionFactory { + public static Connection anotherMethod03() { + return (Connection) holder.get(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingLiteralTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingLiteralTest.java new file mode 100644 index 00000000000..deed8e9030b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingLiteralTest.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.db.jdbc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.RuleChainFactory; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.h2.util.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class JdbcAppenderColumnMappingLiteralTest extends AbstractH2Test { + + @Rule + public final RuleChain rules; + + private final JdbcRule jdbcRule; + + public JdbcAppenderColumnMappingLiteralTest() { + this(new JdbcRule( + JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_TMPDIR, + "CREATE TABLE dsMappingLogEntry (id INTEGER, level VARCHAR(10), logger VARCHAR(255), message VARCHAR(1024), exception CLOB)", + "DROP TABLE IF EXISTS dsMappingLogEntry")); + } + + protected JdbcAppenderColumnMappingLiteralTest(final JdbcRule jdbcRule) { + this.rules = RuleChainFactory.create( + jdbcRule, + new LoggerContextRule( + "org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-literal.xml")); + this.jdbcRule = jdbcRule; + } + + @Test + public void test() throws Exception { + try (final Connection connection = jdbcRule.getConnection()) { + final Error exception = new Error("This is a test."); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (final PrintWriter writer = new PrintWriter(outputStream)) { + exception.printStackTrace(writer); + } + final String stackTrace = outputStream.toString(); + + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig"); + logger.trace("Data source logged message 01."); + logger.fatal("Error from data source 02.", exception); + Thread.sleep(1000); + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = statement.executeQuery("SELECT * FROM dsMappingLogEntry ORDER BY id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + assertEquals("The level column is not correct (1).", "FATAL", resultSet.getNString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getNString("logger")); + assertEquals("The message column is not correct (1).", "Hello World!", resultSet.getString("message")); + assertEquals( + "The exception column is not correct (1).", + stackTrace, + IOUtils.readStringAndClose( + resultSet.getNClob("exception").getCharacterStream(), -1)); + + assertFalse("There should not be two rows.", resultSet.next()); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPatternTest.java new file mode 100644 index 00000000000..a080f203fa4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPatternTest.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.db.jdbc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.RuleChainFactory; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.h2.util.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class JdbcAppenderColumnMappingPatternTest extends AbstractH2Test { + + @Rule + public final RuleChain rules; + + private final JdbcRule jdbcRule; + + public JdbcAppenderColumnMappingPatternTest() { + this(new JdbcRule( + JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM, + "CREATE TABLE dsMappingLogEntry (id INTEGER, level VARCHAR(10), logger VARCHAR(255), message VARCHAR(1024), exception CLOB)", + "DROP TABLE IF EXISTS dsMappingLogEntry")); + } + + protected JdbcAppenderColumnMappingPatternTest(final JdbcRule jdbcRule) { + this.rules = RuleChainFactory.create( + jdbcRule, + new LoggerContextRule( + "org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-pattern.xml")); + this.jdbcRule = jdbcRule; + } + + @Test + public void test() throws Exception { + try (final Connection connection = jdbcRule.getConnection()) { + final Error exception = new Error("This is a test."); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (final PrintWriter writer = new PrintWriter(outputStream)) { + exception.printStackTrace(writer); + } + final String stackTrace = outputStream.toString(); + + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig"); + logger.trace("Data source logged message 01."); + logger.fatal("Error from data source 02.", exception); + Thread.sleep(1000); + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = statement.executeQuery("SELECT * FROM dsMappingLogEntry ORDER BY id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + assertEquals("The level column is not correct (1).", "FATAL", resultSet.getNString("level")); + assertEquals("The logger column is not correct (1).", logger.getName(), resultSet.getNString("logger")); + assertEquals( + "The message column is not correct (1).", + "Error from data source 02.", + resultSet.getString("message")); + assertEquals( + "The exception column is not correct (1).", + stackTrace, + IOUtils.readStringAndClose( + resultSet.getNClob("exception").getCharacterStream(), -1)); + + assertFalse("There should not be two rows.", resultSet.next()); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPropertiesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPropertiesTest.java new file mode 100644 index 00000000000..df6bf6f9dd8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderColumnMappingPropertiesTest.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.appender.db.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URISyntaxException; +import java.net.URL; +import java.sql.Connection; +import java.util.Date; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.db.ColumnMapping; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +public class JdbcAppenderColumnMappingPropertiesTest { + + public Connection getConnection() { + return null; + } + + /** + * Tests the possibility to configure {@link org.apache.logging.log4j.core.appender.db.ColumnMapping} in a + * properties configuration. + * @see #1405 + */ + @Test + void testColumnMapping() throws URISyntaxException { + final URL configLocation = JdbcAppenderColumnMappingPropertiesTest.class.getResource( + "JdbcAppenderColumnMappingPropertiesTest.properties"); + assertThat(configLocation).isNotNull(); + final Configuration config = + PropertiesConfigurationFactory.getInstance().getConfiguration(null, null, configLocation.toURI()); + assertThat(config).isNotNull(); + config.initialize(); + final Appender appender = config.getAppender("Jdbc"); + assertThat(appender).isInstanceOf(JdbcAppender.class); + final JdbcAppender jdbcAppender = (JdbcAppender) appender; + + final ColumnMapping expected = ColumnMapping.newBuilder() + .setName("timestamp") + .setColumnType(Date.class) + .build(); + final ColumnMapping[] mappings = jdbcAppender.getManager().factoryData.columnMappings; + assertThat(mappings).hasSize(1); + assertThat(mappings[0]).isEqualTo(expected); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2DataSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2DataSourceTest.java new file mode 100644 index 00000000000..a9c7e624e20 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2DataSourceTest.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.db.jdbc; + +import java.io.IOException; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.junit.Before; + +/** + * + */ +public class JdbcAppenderH2DataSourceTest extends AbstractJdbcAppenderDataSourceTest { + + @Before + public void afterEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + @Before + public void beforeEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + public JdbcAppenderH2DataSourceTest() { + super(new JdbcRule( + JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM, + "CREATE TABLE dsLogEntry (" + + "id INTEGER, eventDate DATETIME, literalColumn VARCHAR(255), level NVARCHAR(10), " + + "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP" + ")", + "DROP TABLE IF EXISTS dsLogEntry")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2FactoryMethodTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2FactoryMethodTest.java new file mode 100644 index 00000000000..ad49fb552a5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderH2FactoryMethodTest.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.appender.db.jdbc; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.junit.Before; + +/** + * + */ +public class JdbcAppenderH2FactoryMethodTest extends AbstractJdbcAppenderFactoryMethodTest { + + public JdbcAppenderH2FactoryMethodTest() { + super( + new JdbcRule( + JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM, + "CREATE TABLE fmLogEntry (" + + "id INTEGER, eventDate DATETIME, literalColumn VARCHAR(255), level NVARCHAR(10), " + + "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP)", + "DROP TABLE IF EXISTS fmLogEntry"), + "h2"); + } + + @Before + public void afterEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + @Before + public void beforeEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + public static Connection getConnection() throws SQLException { + return JdbcH2TestHelper.getConnectionInMemory(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbDataSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbDataSourceTest.java new file mode 100644 index 00000000000..7170d6f1d8a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbDataSourceTest.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.db.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import org.apache.logging.log4j.core.test.junit.JdbcRule; + +/** + * + */ +public class JdbcAppenderHsqldbDataSourceTest extends AbstractJdbcAppenderDataSourceTest { + + public JdbcAppenderHsqldbDataSourceTest() { + super(new JdbcRule( + new AbstractConnectionSource() { + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:hsqldb:mem:Log4j", "sa", ""); + } + }, + "CREATE TABLE dsLogEntry (" + + "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level VARCHAR(10), " + + "logger VARCHAR(255), message VARCHAR(1024), exception CLOB, anotherDate TIMESTAMP" + + ")", + "DROP TABLE IF EXISTS dsLogEntry")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbFactoryMethodTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbFactoryMethodTest.java new file mode 100644 index 00000000000..a9a5c318012 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderHsqldbFactoryMethodTest.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.appender.db.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import org.apache.logging.log4j.core.test.junit.JdbcRule; + +/** + * + */ +public class JdbcAppenderHsqldbFactoryMethodTest extends AbstractJdbcAppenderFactoryMethodTest { + + public JdbcAppenderHsqldbFactoryMethodTest() { + super( + new JdbcRule( + new AbstractConnectionSource() { + @Override + public Connection getConnection() throws SQLException { + return JdbcAppenderHsqldbFactoryMethodTest.getConnection(); + } + }, + "CREATE TABLE fmLogEntry (" + + "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level VARCHAR(10), " + + "logger VARCHAR(255), message VARCHAR(1024), exception CLOB, anotherDate TIMESTAMP" + + ")", + "DROP TABLE IF EXISTS fmLogEntry"), + "hsqldb"); + } + + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:hsqldb:mem:Log4j", "sa", ""); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.java new file mode 100644 index 00000000000..7923157b5e0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderMapMessageDataSourceTest.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.db.jdbc; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.appender.db.jdbc.JdbcH2TestHelper; +import org.apache.logging.log4j.core.test.junit.JdbcRule; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.message.MapMessage; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * Unit tests {@link MapMessage}s for JdbcAppender using a {@link DataSource} configuration. + */ +public class JdbcAppenderMapMessageDataSourceTest extends AbstractJdbcDataSourceTest { + + @Rule + public final RuleChain rules; + + private final JdbcRule jdbcRule; + + public JdbcAppenderMapMessageDataSourceTest() { + this(new JdbcRule( + JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM, + // @formatter:off + "CREATE TABLE dsLogEntry (Id INTEGER, ColumnA VARCHAR(255), ColumnB VARCHAR(255))", + "DROP TABLE IF EXISTS dsLogEntry")); + // @formatter:on + } + + protected JdbcAppenderMapMessageDataSourceTest(final JdbcRule jdbcRule) { + // @formatter:off + this.rules = RuleChain.emptyRuleChain() + .around(new JndiRule("java:/comp/env/jdbc/TestDataSourceAppender", createMockDataSource())) + .around(jdbcRule) + .around(new LoggerContextRule( + "org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source-map-message.xml")); + // @formatter:on + this.jdbcRule = jdbcRule; + } + + @Before + public void afterEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + @Before + public void beforeEachDeleteDir() throws IOException { + JdbcH2TestHelper.deleteDir(); + } + + private DataSource createMockDataSource() { + try { + final DataSource dataSource = mock(DataSource.class); + given(dataSource.getConnection()) + .willAnswer(invocation -> jdbcRule.getConnectionSource().getConnection()); + return dataSource; + } catch (final SQLException e) { + Throwables.rethrow(e); + throw new InternalError("unreachable"); + } + } + + @Test + public void testDataSourceConfig() throws Exception { + try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) { + final Error exception = new Error("Final error massage is fatal!"); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final PrintWriter writer = new PrintWriter(outputStream); + exception.printStackTrace(writer); + writer.close(); + + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testDataSourceConfig"); + final MapMessage mapMessage = new MapMessage(); + mapMessage.with("Id", 1); + mapMessage.with("ColumnA", "ValueA"); + mapMessage.with("ColumnB", "ValueB"); + logger.info(mapMessage); + + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = + statement.executeQuery("SELECT Id, ColumnA, ColumnB FROM dsLogEntry ORDER BY Id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + Assert.assertEquals(1, resultSet.getInt("Id")); + + assertFalse("There should not be two rows.", resultSet.next()); + } + } + } + + @Test + public void testTruncate() throws SQLException { + try (final Connection connection = jdbcRule.getConnectionSource().getConnection()) { + final Logger logger = LogManager.getLogger(this.getClass().getName() + ".testFactoryMethodConfig"); + // Some drivers and database will not allow more data than the column defines. + // We really need a MySQL databases with a default configuration to test this. + final MapMessage mapMessage = new MapMessage(); + mapMessage.with("Id", 1); + mapMessage.with("ColumnA", StringUtils.repeat('A', 1000)); + mapMessage.with("ColumnB", StringUtils.repeat('B', 1000)); + logger.info(mapMessage); + try (final Statement statement = connection.createStatement(); + final ResultSet resultSet = + statement.executeQuery("SELECT Id, ColumnA, ColumnB FROM dsLogEntry ORDER BY Id")) { + + assertTrue("There should be at least one row.", resultSet.next()); + + Assert.assertEquals(1, resultSet.getInt("Id")); + + assertFalse("There should not be two rows.", resultSet.next()); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderStringSubstitutionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderStringSubstitutionTest.java new file mode 100644 index 00000000000..693aa835797 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppenderStringSubstitutionTest.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.db.jdbc; + +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class JdbcAppenderStringSubstitutionTest { + + private static final String VALUE = "MyTableName"; + private static final String KEY = "Test.TableName"; + + public JdbcAppenderStringSubstitutionTest() { + System.setProperty(KEY, VALUE); + } + + @AfterClass + public static void afterClass() { + System.getProperties().remove(KEY); + } + + @Rule + public final LoggerContextRule rule = + new LoggerContextRule("org/apache/logging/log4j/core/appender/db/jdbc/log4j2-jdbc-string-substitution.xml"); + + @Test + public void test() { + final JdbcAppender appender = rule.getAppender("databaseAppender", JdbcAppender.class); + Assert.assertNotNull(appender); + final JdbcDatabaseManager manager = appender.getManager(); + Assert.assertNotNull(manager); + final String sqlStatement = manager.getSqlStatement(); + Assert.assertFalse(sqlStatement, sqlStatement.contains(KEY)); + Assert.assertTrue(sqlStatement, sqlStatement.contains(VALUE)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java new file mode 100644 index 00000000000..5507c33075b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/JmsAppenderTest.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.Destination; +import javax.jms.MapMessage; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Session; +import javax.jms.TextMessage; +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.core.test.categories.Appenders; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.StringMapMessage; +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.rules.RuleChain; + +@Category(Appenders.Jms.class) +public class JmsAppenderTest { + + private static final String CONNECTION_FACTORY_NAME = "jms/connectionFactory"; + private static final String QUEUE_FACTORY_NAME = "jms/queues"; + private static final String TOPIC_FACTORY_NAME = "jms/topics"; + private static final String DESTINATION_NAME = "jms/destination"; + private static final String DESTINATION_NAME_ML = "jms/destination-ml"; + private static final String QUEUE_NAME = "jms/queue"; + private static final String TOPIC_NAME = "jms/topic"; + private static final String LOG_MESSAGE = "Hello, world!"; + + private final ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + private final Connection connection = mock(Connection.class); + private final Session session = mock(Session.class); + private final Destination destination = mock(Destination.class); + private final Destination destinationMl = mock(Destination.class); + private final MessageProducer messageProducer = mock(MessageProducer.class); + private final MessageProducer messageProducerMl = mock(MessageProducer.class); + private final TextMessage textMessage = mock(TextMessage.class); + private final ObjectMessage objectMessage = mock(ObjectMessage.class); + private final MapMessage mapMessage = mock(MapMessage.class); + + private final JndiRule jndiRule = new JndiRule(createBindings()); + private final LoggerContextRule ctx = new LoggerContextRule("JmsAppenderTest.xml"); + + @Rule + public RuleChain rules = RuleChain.outerRule(jndiRule).around(ctx); + + @AfterClass + public static void afterClass() { + System.clearProperty("log4j2.enableJndiJms"); + } + + @BeforeClass + public static void beforeClass() { + System.setProperty("log4j2.enableJndiJms", "true"); + } + + public JmsAppenderTest() throws Exception { + // this needs to set up before LoggerContextRule + given(connectionFactory.createConnection()).willReturn(connection); + given(connectionFactory.createConnection(anyString(), anyString())).willThrow(IllegalArgumentException.class); + given(connection.createSession(eq(false), eq(Session.AUTO_ACKNOWLEDGE))).willReturn(session); + given(session.createProducer(eq(destination))).willReturn(messageProducer); + given(session.createProducer(eq(destinationMl))).willReturn(messageProducerMl); + given(session.createTextMessage(anyString())).willReturn(textMessage); + given(session.createObjectMessage(isA(Serializable.class))).willReturn(objectMessage); + given(session.createMapMessage()).willReturn(mapMessage); + } + + private Map createBindings() { + final ConcurrentHashMap map = new ConcurrentHashMap<>(); + map.put(CONNECTION_FACTORY_NAME, connectionFactory); + map.put(DESTINATION_NAME, destination); + map.put(DESTINATION_NAME_ML, destinationMl); + map.put(QUEUE_FACTORY_NAME, connectionFactory); + map.put(QUEUE_NAME, destination); + map.put(TOPIC_FACTORY_NAME, connectionFactory); + map.put(TOPIC_NAME, destination); + return map; + } + + private Log4jLogEvent createLogEvent() { + return createLogEvent(new SimpleMessage(LOG_MESSAGE)); + } + + private Log4jLogEvent createLogEvent(final Message message) { + // @formatter:off + return Log4jLogEvent.newBuilder() + .setLoggerName(JmsAppenderTest.class.getName()) + .setLoggerFqcn(JmsAppenderTest.class.getName()) + .setLevel(Level.INFO) + .setMessage(message) + .build(); + // @formatter:on + } + + private Log4jLogEvent createMapMessageLogEvent() { + final StringMapMessage mapMessage = new StringMapMessage(); + return createLogEvent(mapMessage.with("testMesage", LOG_MESSAGE)); + } + + @Before + public void setUp() throws Exception { + // we have 4 appenders all connecting to the same ConnectionFactory + then(connection).should(times(4)).start(); + } + + @Test + public void testAppendToQueue() throws Exception { + final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsAppender"); + final LogEvent event = createLogEvent(); + appender.append(event); + then(session).should().createTextMessage(eq(LOG_MESSAGE)); + then(textMessage).should().setJMSTimestamp(anyLong()); + then(messageProducer).should().send(textMessage); + appender.stop(); + then(session).should().close(); + then(connection).should().close(); + } + + @Test + public void testAppendToQueueWithMessageLayout() throws Exception { + final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsAppender-MessageLayout"); + final LogEvent event = createMapMessageLogEvent(); + appender.append(event); + then(session).should().createMapMessage(); + then(mapMessage).should().setJMSTimestamp(anyLong()); + then(messageProducerMl).should().send(mapMessage); + appender.stop(); + then(session).should().close(); + then(connection).should().close(); + } + + @Test + public void testJmsQueueAppenderCompatibility() throws Exception { + final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsQueueAppender"); + final LogEvent expected = createLogEvent(); + appender.append(expected); + then(session).should().createObjectMessage(eq(expected)); + then(objectMessage).should().setJMSTimestamp(anyLong()); + then(messageProducer).should().send(objectMessage); + appender.stop(); + then(session).should().close(); + then(connection).should().close(); + } + + @Test + public void testJmsTopicAppenderCompatibility() throws Exception { + final JmsAppender appender = (JmsAppender) ctx.getRequiredAppender("JmsTopicAppender"); + final LogEvent expected = createLogEvent(); + appender.append(expected); + then(session).should().createObjectMessage(eq(expected)); + then(objectMessage).should().setJMSTimestamp(anyLong()); + then(messageProducer).should().send(objectMessage); + appender.stop(); + then(session).should().close(); + then(connection).should().close(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppenderTest.java new file mode 100644 index 00000000000..9bd516daa48 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppenderTest.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.awaitility.Awaitility.waitAtMost; +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.Serializable; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +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.filter.AbstractFilter; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.util.ExecutorServices; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.awaitility.core.ConditionTimeoutException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.zeromq.ZMonitor; +import org.zeromq.ZMonitor.Event; +import org.zeromq.ZMonitor.ZEvent; + +@Tag("zeromq") +@Timeout(value = 20, unit = TimeUnit.SECONDS) +@UsingStatusListener +@LoggerContextSource(value = "JeroMqAppenderTest.xml", timeout = 60) +class JeroMqAppenderTest { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private static final String APPENDER_NAME = "JeroMQAppender"; + + private static final int DEFAULT_TIMEOUT_MS = 5000; + + @Test + void testAppenderLifeCycle() { + // do nothing to make sure the appender starts and stops without + // locking up resources. + assertNotNull(JeroMqManager.getContext()); + } + + @Test + void testClientServer(@Named(APPENDER_NAME) final JeroMqAppender appender, final LoggerContext ctx) + throws Exception { + addLoggingFilter(appender); + final Logger logger = ctx.getLogger(getClass()); + final int expectedReceiveCount = 3; + final String endpoint = getTcpEndpoint(appender); + final JeroMqTestClient client = + new JeroMqTestClient(JeroMqManager.getContext(), endpoint, expectedReceiveCount); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final ZMonitor monitor = createMonitor(appender); + boolean connected = false; + try { + final Future> future = executor.submit(client); + waitAtMost(DEFAULT_TIMEOUT_MS, MILLISECONDS) + .until(() -> hasEventOccurred(monitor, Event.HANDSHAKE_PROTOCOL)); + connected = true; + appender.resetSendRcs(); + logger.info("Hello"); + logger.info("Again"); + ThreadContext.put("foo", "bar"); + logger.info("World"); + final List list = future.get(); + assertEquals(expectedReceiveCount, appender.getSendRcTrue()); + assertEquals(0, appender.getSendRcFalse()); + assertEquals("Hello", list.get(0)); + assertEquals("Again", list.get(1)); + assertEquals("barWorld", list.get(2)); + } catch (final ConditionTimeoutException e) { + LOGGER.warn("Timeout reached while waiting for JeroMqTestClient to connect.", e); + } finally { + executor.shutdown(); + if (connected) { + waitAtMost(DEFAULT_TIMEOUT_MS, MILLISECONDS).until(() -> hasEventOccurred(monitor, Event.DISCONNECTED)); + } + monitor.destroy(); + } + } + + @Test + void testMultiThreadedServer(@Named(APPENDER_NAME) final JeroMqAppender appender, final LoggerContext ctx) + throws Exception { + addLoggingFilter(appender); + final Logger logger = ctx.getLogger(getClass()); + final int nThreads = 10; + final int expectedReceiveCount = 2 * nThreads; + final String endpoint = getTcpEndpoint(appender); + final JeroMqTestClient client = + new JeroMqTestClient(JeroMqManager.getContext(), endpoint, expectedReceiveCount); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final ZMonitor monitor = createMonitor(appender); + boolean connected = false; + try { + final Future> future = executor.submit(client); + waitAtMost(DEFAULT_TIMEOUT_MS, MILLISECONDS) + .until(() -> hasEventOccurred(monitor, Event.HANDSHAKE_PROTOCOL)); + connected = true; + appender.resetSendRcs(); + final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads); + for (int i = 0; i < 10.; i++) { + final int nr = i; + fixedThreadPool.submit(() -> { + logger.info("Hello ({})", nr); + logger.info("Again ({})", nr); + }); + } + final List list = future.get(); + assertEquals(expectedReceiveCount, appender.getSendRcTrue()); + assertEquals(0, appender.getSendRcFalse()); + int hello = 0; + int again = 0; + for (final String string : list) { + if (string.startsWith("Hello")) { + hello++; + } else if (string.startsWith("Again")) { + again++; + } else { + fail("Unexpected message: " + string); + } + } + assertEquals(nThreads, hello); + assertEquals(nThreads, again); + } finally { + ExecutorServices.shutdown( + executor, DEFAULT_TIMEOUT_MS, MILLISECONDS, JeroMqAppenderTest.class.getSimpleName()); + if (connected) { + waitAtMost(DEFAULT_TIMEOUT_MS, MILLISECONDS).until(() -> hasEventOccurred(monitor, Event.DISCONNECTED)); + } + monitor.destroy(); + } + } + + @Test + void testServerOnly(@Named(APPENDER_NAME) final JeroMqAppender appender, final LoggerContext ctx) { + addLoggingFilter(appender); + final Logger logger = ctx.getLogger(getClass()); + appender.resetSendRcs(); + logger.info("Hello"); + logger.info("Again"); + assertEquals(2, appender.getSendRcTrue()); + assertEquals(0, appender.getSendRcFalse()); + } + + private String getTcpEndpoint(final JeroMqAppender appender) { + for (final String endpoint : appender.getManager().getEndpoints()) { + if (endpoint.startsWith("tcp://0.0.0.0")) { + return endpoint.replace("0.0.0.0", "localhost"); + } + } + fail("No TCP endpoint found."); + return null; + } + + private boolean hasEventOccurred(final ZMonitor monitor, final Event eventType) { + ZEvent event; + while ((event = monitor.nextEvent(false)) != null) { + LOGGER.info("JeroMqAppender received an event of type {}", event); + if (event.type == eventType) { + return true; + } + } + return false; + } + + private ZMonitor createMonitor(final JeroMqAppender appender) { + final ZMonitor monitor = + new ZMonitor(JeroMqManager.getZContext(), appender.getManager().getSocket()); + monitor.add(Event.HANDSHAKE_PROTOCOL, Event.DISCONNECTED); + monitor.start(); + LOGGER.info("Starting ZMonitor for JeroMqAppender {}.", appender.getName()); + return monitor; + } + + private void addLoggingFilter(final JeroMqAppender appender) { + final Layout layout = appender.getLayout(); + appender.addFilter(new AbstractFilter() { + + @Override + public Result filter(LogEvent event) { + LOGGER.info("JeroMqAppender sent a message: {}.", layout.toSerializable(event)); + return Result.NEUTRAL; + } + }); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqTestClient.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqTestClient.java new file mode 100644 index 00000000000..c40514e93b9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqTestClient.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.mom.jeromq; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.zeromq.SocketType; +import org.zeromq.ZMQ; + +class JeroMqTestClient implements Callable> { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final ZMQ.Context context; + + private final String endpoint; + private final List messages; + private final int receiveCount; + + JeroMqTestClient(final ZMQ.Context context, final String endpoint, final int receiveCount) { + this.context = context; + this.endpoint = endpoint; + this.receiveCount = receiveCount; + this.messages = new ArrayList<>(receiveCount); + } + + @Override + public List call() { + try (final ZMQ.Socket subscriber = context.socket(SocketType.SUB)) { + LOGGER.info("Starting JeroMqTestClient."); + subscriber.connect(endpoint); + subscriber.subscribe(ZMQ.SUBSCRIPTION_ALL); + LOGGER.info("Subscribing JeroMqTestClient to JeroMqAppender."); + for (int messageNum = 0; + messageNum < receiveCount && !Thread.currentThread().isInterrupted(); + messageNum++) { + // Use trim to remove the tailing '0' character + final String message = subscriber.recvStr(0).trim(); + LOGGER.debug("JeroMqTestClient received a message: {}.", message); + messages.add(message); + } + } + return messages; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderCloseTimeoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderCloseTimeoutTest.java new file mode 100644 index 00000000000..3a54a91d0ab --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderCloseTimeoutTest.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.mom.kafka; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.apache.kafka.clients.producer.MockProducer; +import org.apache.kafka.common.serialization.ByteArraySerializer; +import org.apache.kafka.common.serialization.Serializer; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.test.categories.Appenders; +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 org.junit.experimental.categories.Category; + +@Category(Appenders.Kafka.class) +public class KafkaAppenderCloseTimeoutTest { + + private static final Serializer SERIALIZER = new ByteArraySerializer(); + + private static final MockProducer kafka = + new MockProducer(true, SERIALIZER, SERIALIZER) { + @Override + public void close() { + try { + Thread.sleep(3000); + } catch (InterruptedException ignore) { + // NOP + } + } + + // @Override in version 3.3.1 + public void close(final Duration timeout) { + try { + Thread.sleep(timeout.toMillis()); + } catch (InterruptedException ignore) { + // NOP + } + } + + // @Override in version 1.1.1 + public void close(final long timeout, final TimeUnit timeUnit) { + try { + Thread.sleep(timeUnit.toMillis(timeout)); + } catch (InterruptedException ignore) { + // NOP + } + } + }; + + @BeforeClass + public static void setUpClass() { + KafkaManager.producerFactory = config -> kafka; + } + + @Rule + public LoggerContextRule ctx = new LoggerContextRule("KafkaAppenderCloseTimeoutTest.xml"); + + @Before + public void setUp() { + kafka.clear(); + } + + @Test(timeout = 2000) + public void testClose() { + final Appender appender = ctx.getRequiredAppender("KafkaAppender"); + appender.stop(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderTest.java new file mode 100644 index 00000000000..ef8c24edf00 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppenderTest.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.appender.mom.kafka; + +import static org.junit.Assert.assertArrayEquals; +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 java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.kafka.clients.producer.MockProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.ByteArraySerializer; +import org.apache.kafka.common.serialization.Serializer; +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.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.test.categories.Appenders; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(Appenders.Kafka.class) +public class KafkaAppenderTest { + + private static final Serializer SERIALIZER = new ByteArraySerializer(); + + private static final MockProducer kafka = + new MockProducer(true, SERIALIZER, SERIALIZER) { + + @Override + public synchronized Future send(final ProducerRecord record) { + + final Future retVal = super.send(record); + + final boolean isRetryTest = "true".equals(ThreadContext.get("KafkaAppenderWithRetryCount")); + if (isRetryTest) { + try { + throw new TimeoutException(); + } catch (TimeoutException e) { + // TODO Auto-generated catch block + throw new RuntimeException(e); + } + } + + return retVal; + } + + // @Override in version 1.1.1 + public void close(final long timeout, final TimeUnit timeUnit) { + // Intentionally do not close in order to reuse + } + + // @Override in version 3.3.1 + public void close(final Duration timeout) { + // Intentionally do no close in order to reuse + } + }; + + private static final String LOG_MESSAGE = "Hello, world!"; + private static final String TOPIC_NAME = "kafka-topic"; + private static final int RETRY_COUNT = 3; + + private static Log4jLogEvent createLogEvent() { + return Log4jLogEvent.newBuilder() + .setLoggerName(KafkaAppenderTest.class.getName()) + .setLoggerFqcn(KafkaAppenderTest.class.getName()) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage(LOG_MESSAGE)) + .build(); + } + + @BeforeClass + public static void setUpClass() { + KafkaManager.producerFactory = config -> kafka; + } + + @Rule + public LoggerContextRule ctx = new LoggerContextRule("KafkaAppenderTest.xml"); + + @Before + public void setUp() { + kafka.clear(); + } + + @Test + public void testAppendWithLayout() { + final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithLayout"); + appender.append(createLogEvent()); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + assertNull(item.key()); + assertEquals("[" + LOG_MESSAGE + "]", new String(item.value(), StandardCharsets.UTF_8)); + } + + @Test + public void testAppendWithSerializedLayout() { + final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithSerializedLayout"); + final LogEvent logEvent = createLogEvent(); + appender.append(logEvent); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + assertNull(item.key()); + final byte[] data = item.value(); + assertEquals( + LOG_MESSAGE, SerialUtil.deserialize(data).getMessage().getFormattedMessage()); + } + + @Test + public void testAsyncAppend() { + final Appender appender = ctx.getRequiredAppender("AsyncKafkaAppender"); + appender.append(createLogEvent()); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + assertNull(item.key()); + assertEquals(LOG_MESSAGE, new String(item.value(), StandardCharsets.UTF_8)); + } + + @Test + public void testAppendWithKey() { + final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithKey"); + final LogEvent logEvent = createLogEvent(); + appender.append(logEvent); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + final byte[] keyValue = "key".getBytes(StandardCharsets.UTF_8); + assertEquals(Long.valueOf(logEvent.getTimeMillis()), item.timestamp()); + assertArrayEquals(item.key(), keyValue); + assertEquals(LOG_MESSAGE, new String(item.value(), StandardCharsets.UTF_8)); + } + + @Test + public void testAppendWithKeyLookup() { + final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithKeyLookup"); + final LogEvent logEvent = createLogEvent(); + final Date date = new Date(); + final SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy"); + appender.append(logEvent); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + final byte[] keyValue = format.format(date).getBytes(StandardCharsets.UTF_8); + assertEquals(Long.valueOf(logEvent.getTimeMillis()), item.timestamp()); + assertArrayEquals(item.key(), keyValue); + assertEquals(LOG_MESSAGE, new String(item.value(), StandardCharsets.UTF_8)); + } + + @Test + public void testAppendWithRetryCount() { + try { + ThreadContext.put("KafkaAppenderWithRetryCount", "true"); + final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithRetryCount"); + final LogEvent logEvent = createLogEvent(); + appender.append(logEvent); + + final List> history = kafka.history(); + assertEquals(RETRY_COUNT + 1, history.size()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + ThreadContext.clearMap(); + } + } + + @Test + public void testAppenderNoEventTimestamp() { + final Appender appender = ctx.getRequiredAppender("KafkaAppenderNoEventTimestamp"); + final LogEvent logEvent = createLogEvent(); + appender.append(logEvent); + final List> history = kafka.history(); + assertEquals(1, history.size()); + final ProducerRecord item = history.get(0); + assertNotNull(item); + assertEquals(TOPIC_NAME, item.topic()); + final byte[] keyValue = "key".getBytes(StandardCharsets.UTF_8); + assertArrayEquals(item.key(), keyValue); + assertNotEquals(Long.valueOf(logEvent.getTimeMillis()), item.timestamp()); + assertEquals(LOG_MESSAGE, new String(item.value(), StandardCharsets.UTF_8)); + } + + // public void shouldRetryWhenTimeoutExceptionOccursOnSend() throws Exception { + // final AtomicInteger attempt = new AtomicInteger(0); + // final RecordCollectorImpl collector = new RecordCollectorImpl( + // new MockProducer(cluster, true, new DefaultPartitioner(), byteArraySerializer, + // byteArraySerializer) { + // @Override + // public synchronized Future send(final ProducerRecord record, final Callback + // callback) { + // if (attempt.getAndIncrement() == 0) { + // throw new TimeoutException(); + // } + // return super.send(record, callback); + // } + // }, + // "test"); + // + // collector.send("topic1", "3", "0", null, stringSerializer, stringSerializer, streamPartitioner); + // final Long offset = collector.offsets().get(new TopicPartition("topic1", 0)); + // assertEquals(Long.valueOf(0L), offset); + // } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManagerProducerThreadLeakTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManagerProducerThreadLeakTest.java new file mode 100644 index 00000000000..9e4b6b14a79 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManagerProducerThreadLeakTest.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.mom.kafka; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Verifies that restarting the {@link LoggerContext} doesn't cause + * {@link KafkaManager} to leak threads. + * + * @see LOG4J2-2916 + */ +@Tag("Appenders.Kafka") +@LoggerContextSource("KafkaManagerProducerThreadLeakTest.xml") +class KafkaManagerProducerThreadLeakTest { + + @Test + void context_restart_shouldnt_leak_producer_threads(final LoggerContext context) { + + // Determine the initial number of threads. + final int initialThreadCount = kafkaProducerThreadCount(); + + // Perform context restarts. + final int contextRestartCount = 3; + for (int i = 0; i < contextRestartCount; i++) { + context.reconfigure(); + } + + // Verify the final thread count. + final int lastThreadCount = kafkaProducerThreadCount(); + assertEquals(initialThreadCount, lastThreadCount); + } + + private static int kafkaProducerThreadCount() { + final long threadCount = Thread.getAllStackTraces().keySet().stream() + .filter(thread -> thread.getName().startsWith("kafka-producer")) + .count(); + return Math.toIntExact(threadCount); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..c3e33ffaf64 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.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.nosql; + +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 org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NoSqlAppenderTest { + + @Mock + private NoSqlProvider provider; + + @Test + void testNoProvider() { + final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, null); + + assertNull(appender, "The appender should be null."); + } + + @Test + void testProvider() { + final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, provider); + + assertNotNull(appender, "The appender should not be null."); + assertEquals( + "myName01{ manager=noSqlManager{ description=myName01, bufferSize=0, provider=" + provider + " } }", + appender.toString(), + "The toString value is not correct."); + + appender.stop(); + } + + @Test + void testProviderBuffer() { + final NoSqlAppender appender = NoSqlAppender.createAppender("anotherName02", null, null, "25", provider); + + assertNotNull(appender, "The appender should not be null."); + assertEquals( + "anotherName02{ manager=noSqlManager{ description=anotherName02, bufferSize=25, provider=" + provider + + " } }", + appender.toString(), + "The toString value is not correct."); + + 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 75% 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..e9c53a37064 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 @@ -1,21 +1,30 @@ /* * 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.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; @@ -23,7 +32,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; @@ -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; @@ -41,53 +49,41 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.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; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class NoSqlDatabaseManagerTest { + @Mock private NoSqlConnection, DefaultNoSqlObject> connection; + @Mock private NoSqlProvider, DefaultNoSqlObject>> provider; + @Mock private Message message; + @Captor private ArgumentCaptor>> captor; @Rule public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule(); + @Rule public final ExpectedException expectedException = ExpectedException.none(); @Before public void setUp() { given(provider.getConnection()).willReturn(connection); - given(connection.createObject()).willAnswer(new Answer() { - @Override - public DefaultNoSqlObject answer(final InvocationOnMock invocation) throws Throwable { - return new DefaultNoSqlObject(); - } - }); - given(connection.createList(anyInt())).willAnswer(new Answer() { - @Override - public DefaultNoSqlObject[] answer(final InvocationOnMock invocation) throws Throwable { - return new DefaultNoSqlObject[invocation.getArgument(0)]; - } - }); + given(connection.createObject()).willAnswer(invocation -> new DefaultNoSqlObject()); + given(connection.createList(anyInt())) + .willAnswer(invocation -> new DefaultNoSqlObject[invocation.getArgument(0)]); } @Test public void testConnection() { - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { assertNotNull("The manager should not be null.", manager); @@ -99,8 +95,8 @@ public void testConnection() { @Test public void testWriteInternalNotConnected01() { - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { expectedException.expect(AppenderLoggingException.class); manager.writeInternal(mock(LogEvent.class), null); } @@ -110,8 +106,8 @@ public void testWriteInternalNotConnected01() { public void testWriteInternalNotConnected02() { given(connection.isClosed()).willReturn(true); - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { manager.startup(); manager.connectAndStart(); @@ -127,23 +123,23 @@ public void testWriteInternal01() { given(connection.isClosed()).willReturn(false); given(message.getFormattedMessage()).willReturn("My formatted message 01."); - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { manager.startup(); manager.connectAndStart(); then(provider).should().getConnection(); final LogEvent event = Log4jLogEvent.newBuilder() - .setLevel(Level.WARN) - .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal01") - .setMessage(message) - .setSource(new StackTraceElement("com.foo.Bar", "testMethod01", "Bar.java", 15)) - .setThreadId(1L) - .setThreadName("MyThread-A") - .setThreadPriority(1) - .setTimeMillis(1234567890123L) - .build(); + .setLevel(Level.WARN) + .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal01") + .setMessage(message) + .setSource(new StackTraceElement("com.foo.Bar", "testMethod01", "Bar.java", 15)) + .setThreadId(1L) + .setThreadName("MyThread-A") + .setThreadPriority(1) + .setTimeMillis(1234567890123L) + .build(); manager.writeInternal(event, null); then(connection).should().insertObject(captor.capture()); @@ -154,8 +150,8 @@ public void testWriteInternal01() { assertNotNull("The unwrapped object should not be null.", object); assertEquals("The level is not correct.", Level.WARN, object.get("level")); - assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal01", - object.get("loggerName")); + assertEquals( + "The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal01", object.get("loggerName")); assertEquals("The message is not correct.", "My formatted message 01.", object.get("message")); assertEquals("The thread is not correct.", "MyThread-A", object.get("threadName")); assertEquals("The millis is not correct.", 1234567890123L, object.get("millis")); @@ -176,7 +172,6 @@ public void testWriteInternal01() { assertTrue("The context map should be empty.", ((Map) object.get("contextMap")).isEmpty()); assertTrue("The context stack should be null.", ((Collection) object.get("contextStack")).isEmpty()); - } } @@ -185,8 +180,8 @@ public void testWriteInternal02() { given(connection.isClosed()).willReturn(false); given(message.getFormattedMessage()).willReturn("Another cool message 02."); - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { manager.startup(); manager.connectAndStart(); @@ -203,19 +198,19 @@ public void testWriteInternal02() { ThreadContext.clearStack(); final LogEvent event = Log4jLogEvent.newBuilder() - .setLevel(Level.DEBUG) - .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02") - .setMessage(message) - .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)) - .setMarker(MarkerManager.getMarker("LoneMarker")) - .setThreadId(1L) - .setThreadName("AnotherThread-B") - .setThreadPriority(1) - .setTimeMillis(987654321564L) - .setThrown(exception) - .setContextData(ContextDataFactory.createContextData(context)) - .setContextStack(stack) - .build(); + .setLevel(Level.DEBUG) + .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02") + .setMessage(message) + .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)) + .setMarker(MarkerManager.getMarker("LoneMarker")) + .setThreadId(1L) + .setThreadName("AnotherThread-B") + .setThreadPriority(1) + .setTimeMillis(987654321564L) + .setThrown(exception) + .setContextData(ContextDataFactory.createContextData(context)) + .setContextStack(stack) + .build(); manager.writeInternal(event, null); then(connection).should().insertObject(captor.capture()); @@ -226,8 +221,8 @@ public void testWriteInternal02() { assertNotNull("The unwrapped object should not be null.", object); assertEquals("The level is not correct.", Level.DEBUG, object.get("level")); - assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", - object.get("loggerName")); + assertEquals( + "The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", object.get("loggerName")); assertEquals("The message is not correct.", "Another cool message 02.", object.get("message")); assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName")); assertEquals("The millis is not correct.", 987654321564L, object.get("millis")); @@ -255,18 +250,20 @@ public void testWriteInternal02() { assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List); @SuppressWarnings("unchecked") final List> stackTrace = (List>) thrown.get("stackTrace"); - assertEquals("The thrown stack trace length is not correct.", exception.getStackTrace().length, - stackTrace.size()); + assertEquals( + "The thrown stack trace length is not correct.", + exception.getStackTrace().length, + stackTrace.size()); for (int i = 0; i < exception.getStackTrace().length; i++) { final StackTraceElement e1 = exception.getStackTrace()[i]; final Map e2 = stackTrace.get(i); assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); - assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), - e2.get("methodName")); + assertEquals( + "Element method name [" + i + "] is not correct.", e1.getMethodName(), e2.get("methodName")); assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); - assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), - e2.get("lineNumber")); + assertEquals( + "Element line number [" + i + "] is not correct.", e1.getLineNumber(), e2.get("lineNumber")); } assertNull("The thrown should have no cause.", thrown.get("cause")); @@ -283,8 +280,8 @@ public void testWriteInternal03() { given(connection.isClosed()).willReturn(false); given(message.getFormattedMessage()).willReturn("Another cool message 02."); - try (final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, - provider)) { + try (final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager("name", 0, provider, null, null)) { manager.startup(); manager.connectAndStart(); @@ -302,21 +299,23 @@ public void testWriteInternal03() { ThreadContext.clearStack(); final LogEvent event = Log4jLogEvent.newBuilder() - .setLevel(Level.DEBUG) - .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02") - .setMessage(message) - .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)) - .setMarker(MarkerManager.getMarker("AnotherMarker").addParents( - MarkerManager.getMarker("Parent1").addParents(MarkerManager.getMarker("GrandParent1")), - MarkerManager.getMarker("Parent2"))) - .setThreadId(1L) - .setThreadName("AnotherThread-B") - .setThreadPriority(1) - .setTimeMillis(987654321564L) - .setThrown(exception2) - .setContextData(ContextDataFactory.createContextData(context)) - .setContextStack(stack) - .build(); + .setLevel(Level.DEBUG) + .setLoggerName("com.foo.NoSQLDbTest.testWriteInternal02") + .setMessage(message) + .setSource(new StackTraceElement("com.bar.Foo", "anotherMethod03", "Foo.java", 9)) + .setMarker(MarkerManager.getMarker("AnotherMarker") + .addParents( + MarkerManager.getMarker("Parent1") + .addParents(MarkerManager.getMarker("GrandParent1")), + MarkerManager.getMarker("Parent2"))) + .setThreadId(1L) + .setThreadName("AnotherThread-B") + .setThreadPriority(1) + .setTimeMillis(987654321564L) + .setThrown(exception2) + .setContextData(ContextDataFactory.createContextData(context)) + .setContextStack(stack) + .build(); manager.writeInternal(event, null); then(connection).should().insertObject(captor.capture()); @@ -327,8 +326,8 @@ public void testWriteInternal03() { assertNotNull("The unwrapped object should not be null.", object); assertEquals("The level is not correct.", Level.DEBUG, object.get("level")); - assertEquals("The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", - object.get("loggerName")); + assertEquals( + "The logger is not correct.", "com.foo.NoSQLDbTest.testWriteInternal02", object.get("loggerName")); assertEquals("The message is not correct.", "Another cool message 02.", object.get("message")); assertEquals("The thread is not correct.", "AnotherThread-B", object.get("threadName")); assertEquals("The millis is not correct.", 987654321564L, object.get("millis")); @@ -382,18 +381,20 @@ public void testWriteInternal03() { assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List); @SuppressWarnings("unchecked") final List> stackTrace = (List>) thrown.get("stackTrace"); - assertEquals("The thrown stack trace length is not correct.", exception2.getStackTrace().length, - stackTrace.size()); + assertEquals( + "The thrown stack trace length is not correct.", + exception2.getStackTrace().length, + stackTrace.size()); for (int i = 0; i < exception2.getStackTrace().length; i++) { final StackTraceElement e1 = exception2.getStackTrace()[i]; final Map e2 = stackTrace.get(i); assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); - assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), - e2.get("methodName")); + assertEquals( + "Element method name [" + i + "] is not correct.", e1.getMethodName(), e2.get("methodName")); assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); - assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), - e2.get("lineNumber")); + assertEquals( + "Element line number [" + i + "] is not correct.", e1.getLineNumber(), e2.get("lineNumber")); } assertTrue("The thrown cause should be a map.", thrown.get("cause") instanceof Map); @SuppressWarnings("unchecked") @@ -403,18 +404,20 @@ public void testWriteInternal03() { assertTrue("The cause stack trace should be a list.", cause.get("stackTrace") instanceof List); @SuppressWarnings("unchecked") final List> causeStackTrace = (List>) cause.get("stackTrace"); - assertEquals("The cause stack trace length is not correct.", exception1.getStackTrace().length, - causeStackTrace.size()); + assertEquals( + "The cause stack trace length is not correct.", + exception1.getStackTrace().length, + causeStackTrace.size()); for (int i = 0; i < exception1.getStackTrace().length; i++) { final StackTraceElement e1 = exception1.getStackTrace()[i]; final Map e2 = causeStackTrace.get(i); assertEquals("Element class name [" + i + "] is not correct.", e1.getClassName(), e2.get("className")); - assertEquals("Element method name [" + i + "] is not correct.", e1.getMethodName(), - e2.get("methodName")); + assertEquals( + "Element method name [" + i + "] is not correct.", e1.getMethodName(), e2.get("methodName")); assertEquals("Element file name [" + i + "] is not correct.", e1.getFileName(), e2.get("fileName")); - assertEquals("Element line number [" + i + "] is not correct.", e1.getLineNumber(), - e2.get("lineNumber")); + assertEquals( + "Element line number [" + i + "] is not correct.", e1.getLineNumber(), e2.get("lineNumber")); } assertNull("The cause should have no cause.", cause.get("cause")); diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..69f67c82d91 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.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.appender.rewrite; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.core.util.KeyValuePair; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link LoggerNameLevelRewritePolicy}. + * + * @since 2.4 + */ +class LoggerNameLevelRewritePolicyTest { + + @Test + void testUpdate() { + final KeyValuePair[] rewrite = + new KeyValuePair[] {new KeyValuePair("INFO", "DEBUG"), new KeyValuePair("WARN", "INFO")}; + final String loggerNameRewrite = "com.foo.bar"; + LogEvent logEvent = Log4jLogEvent.newBuilder() + .setLoggerName(loggerNameRewrite) + .setLoggerFqcn("LoggerNameLevelRewritePolicyTest.testUpdate()") + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("Test")) + .setThrown(new RuntimeException("test")) + .setThreadName("none") + .setTimeMillis(1) + .build(); + final LoggerNameLevelRewritePolicy updatePolicy = + LoggerNameLevelRewritePolicy.createPolicy(loggerNameRewrite, rewrite); + LogEvent rewritten = updatePolicy.rewrite(logEvent); + 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); + 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); + 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); + assertEquals(Level.WARN, rewritten.getLevel()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..5401b8317e0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.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.logging.log4j.core.appender.rewrite; + +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; + +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; +import org.apache.logging.log4j.core.impl.ContextDataFactory; +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.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.spi.MutableThreadContextStack; +import org.apache.logging.log4j.spi.ThreadContextStack; +import org.apache.logging.log4j.util.StringMap; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class MapRewritePolicyTest { + private static final StringMap stringMap = ContextDataFactory.createContextData(); + private static Map map = new HashMap<>(); + private static KeyValuePair[] rewrite; + private static LogEvent logEvent0, logEvent1, logEvent2, logEvent3; + + @BeforeAll + static void setupClass() { + stringMap.putValue("test1", "one"); + stringMap.putValue("test2", "two"); + map = stringMap.toMap(); + logEvent0 = Log4jLogEvent.newBuilder() // + .setLoggerName("test") // + .setContextData(stringMap) // + .setLoggerFqcn("MapRewritePolicyTest.setupClass()") // + .setLevel(Level.ERROR) // + .setMessage(new SimpleMessage("Test")) // + .setThrown(new RuntimeException("test")) // + .setThreadName("none") + .setSource(new StackTraceElement("MapRewritePolicyTest", "setupClass", "MapRewritePolicyTest", 28)) + .setTimeMillis(2) + .build(); + + logEvent1 = ((Log4jLogEvent) logEvent0) + .asBuilder() // + .setMessage(new StringMapMessage(map)) // + .setSource(new StackTraceElement("MapRewritePolicyTest", "setupClass", "MapRewritePolicyTest", 29)) // + .build(); + + final ThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>(map.values())); + logEvent2 = ((Log4jLogEvent) logEvent0) + .asBuilder() // + .setContextStack(stack) // + .setMarker(MarkerManager.getMarker("test")) // + .setLevel(Level.TRACE) // + .setMessage(new StructuredDataMessage("test", "Nothing", "test", map)) // + .setTimeMillis(20000000) // + .setSource(new StackTraceElement("MapRewritePolicyTest", "setupClass", "MapRewritePolicyTest", 30)) // + .build(); + logEvent3 = ((Log4jLogEvent) logEvent0) + .asBuilder() // + .setContextStack(stack) // + .setLevel(Level.ALL) // + .setMessage(new StringMapMessage(map)) // + .setTimeMillis(Long.MAX_VALUE) // + .setSource(new StackTraceElement("MapRewritePolicyTest", "setupClass", "MapRewritePolicyTest", 31)) // + .build(); + rewrite = new KeyValuePair[] {new KeyValuePair("test2", "2"), new KeyValuePair("test3", "three")}; + } + + @Test + void addTest() { + final MapRewritePolicy addPolicy = MapRewritePolicy.createPolicy("Add", rewrite); + LogEvent rewritten = addPolicy.rewrite(logEvent0); + compareLogEvents(logEvent0, rewritten); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); + + rewritten = addPolicy.rewrite(logEvent1); + compareLogEvents(logEvent1, rewritten); + checkAdded(((StringMapMessage) rewritten.getMessage()).getData()); + + rewritten = addPolicy.rewrite(logEvent2); + compareLogEvents(logEvent2, rewritten); + checkAdded(((StructuredDataMessage) rewritten.getMessage()).getData()); + + rewritten = addPolicy.rewrite(logEvent3); + compareLogEvents(logEvent3, rewritten); + checkAdded(((StringMapMessage) rewritten.getMessage()).getData()); + } + + @Test + void updateTest() { + final MapRewritePolicy updatePolicy = MapRewritePolicy.createPolicy("Update", rewrite); + LogEvent rewritten = updatePolicy.rewrite(logEvent0); + compareLogEvents(logEvent0, rewritten); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); + + rewritten = updatePolicy.rewrite(logEvent1); + compareLogEvents(logEvent1, rewritten); + checkUpdated(((StringMapMessage) rewritten.getMessage()).getData()); + + rewritten = updatePolicy.rewrite(logEvent2); + compareLogEvents(logEvent2, rewritten); + checkUpdated(((StructuredDataMessage) rewritten.getMessage()).getData()); + + rewritten = updatePolicy.rewrite(logEvent3); + compareLogEvents(logEvent3, rewritten); + checkUpdated(((StringMapMessage) rewritten.getMessage()).getData()); + } + + @Test + void defaultIsAdd() { + final MapRewritePolicy addPolicy = MapRewritePolicy.createPolicy(null, rewrite); + LogEvent rewritten = addPolicy.rewrite(logEvent0); + compareLogEvents(logEvent0, rewritten); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); + + rewritten = addPolicy.rewrite(logEvent1); + compareLogEvents(logEvent1, rewritten); + checkAdded(((StringMapMessage) rewritten.getMessage()).getData()); + + rewritten = addPolicy.rewrite(logEvent2); + compareLogEvents(logEvent2, rewritten); + checkAdded(((StructuredDataMessage) rewritten.getMessage()).getData()); + + rewritten = addPolicy.rewrite(logEvent3); + compareLogEvents(logEvent3, rewritten); + checkAdded(((StringMapMessage) rewritten.getMessage()).getData()); + } + + private void checkAdded(final Map addedMap) { + assertThat("unwanted entry change", addedMap, hasEntry("test1", "one")); + assertThat("existing entry not updated", addedMap, hasEntry("test2", "2")); + assertThat("new entry not added", addedMap, hasEntry("test3", "three")); + assertThat("wrong size", addedMap, hasSize(3)); + } + + private void checkUpdated(final Map updatedMap) { + assertThat("unwanted entry change", updatedMap, hasEntry("test1", "one")); + assertThat("existing entry not updated", updatedMap, hasEntry("test2", "2")); + assertThat("wrong size", updatedMap, hasSize(2)); + } + + private void compareLogEvents(final LogEvent orig, final LogEvent changed) { + // Ensure that everything but the Mapped Data is still the same + 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.getThrown().getStackTrace(), + changed.getThrown() == null ? null : changed.getThrown().getStackTrace(), + "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..226b1952c3b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.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.appender.rewrite; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +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 java.util.List; +import java.util.Map; +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.appender.ListAppender; +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.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-rewrite.xml") +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 + 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 + 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(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + assertFalse(list.get(0).contains("{user."), "Did not resolve user name"); + } + + @Test + 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..ece0e02b47a 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rewrite; 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..65f3ccb910f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.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.rolling; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +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 + void before() { + configuration = new NullConfiguration(); + } + + private CronTriggeringPolicy createPolicy() { + return CronTriggeringPolicy.createPolicy(configuration, Boolean.TRUE.toString(), CRON_EXPRESSION); + } + + private DefaultRolloverStrategy createStrategy() { + return DefaultRolloverStrategy.createStrategy("7", "1", "max", null, null, false, configuration); + } + + 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 + void testBuilderOnce() { + testBuilder(); + } + + /** + * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE + */ + @Test + void testLoggerContextAndBuilder() { + Configurator.initialize(configuration); + testBuilder(); + } + + /** + * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE + */ + @Test + void testRollingRandomAccessFileAppender() { + // @formatter:off + 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 + void testBuilderSequence() { + testBuilder(); + testBuilder(); + } + + private void testFactoryMethod() { + final CronTriggeringPolicy triggerPolicy = createPolicy(); + final DefaultRolloverStrategy rolloverStrategy = createStrategy(); + + try (final 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)) { + // trigger rollover + fileManager.initialize(); + fileManager.rollover(); + } + } + + /** + * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is + * true. + */ + @Test + void testFactoryMethodOnce() { + testFactoryMethod(); + } + + @Test + void testFactoryMethodSequence() { + testFactoryMethod(); + 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..e0b7c487f2e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/EligibleFilesTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.nio.file.Path; +import java.util.Map; +import org.apache.logging.log4j.core.pattern.NotANumber; +import org.junit.jupiter.api.Test; + +/** + * Test getEligibleFiles method. + */ +class EligibleFilesTest { + + @Test + void runTest() { + 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); + assertFalse(files.isEmpty(), "No files found"); + assertEquals(30, files.size(), "Incorrect number of files found. Should be 30, was " + files.size()); + } + + @Test + void runTestWithPlusCharacter() { + 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); + assertFalse(files.isEmpty(), "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..9d48e6492cb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/FileSizeTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Tests {@link FileSize}. + */ +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..548cc1e5076 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.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.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +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 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.util.datetime.FastDateFormat; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests {@link OnStartupTriggeringPolicy}. + */ +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 + 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); + + /* + * POSIX does not define a file creation timestamp. + * Depending on the system `creationTime` might be: + * * 0, + * * the last modification time + * * or the time the file was actually created. + * + * This test fails if the latter occurs, since the file is created after the JVM. + */ + final FileTime creationTime = attrs.readAttributes().creationTime(); + assumeTrue(creationTime.equals(fileTime) || creationTime.toMillis() == 0L); + + 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 (final Stream contents = Files.list(tempDir)) { + files = contents.map(Path::toString).collect(Collectors.joining(", ", "[", "]")); + } + assertTrue(Files.exists(target), target + ", files = " + files); + assertEquals(0, Files.size(target), target.toString()); + assertTrue(Files.exists(rolled), "Missing: " + rolled + ", files on disk = " + files); + assertEquals(size, Files.size(rolled), rolled.toString()); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..36341b77474 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Tests the PatternProcessor class. + */ +class PatternProcessorTest { + + private static Instant parseLocalDateTime(final String text) { + return LocalDateTime.parse(text).atZone(ZoneId.systemDefault()).toInstant(); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + 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, 2); + cal.set(Calendar.SECOND, 15); + + final StringBuilder buf = new StringBuilder(); + pp.formatFileName(buf, cal.getTime(), 23); + assertEquals("c:\\test\\new/app-16-02-15.log", buf.toString()); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-04T11:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour2() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T23:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-05T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeHourlyReturnsFirstMinuteOfNextHourDstStart() { + // America/Chicago 2014 - DST start - Mar 9 02:00 + // during winter GMT-6 + // during summer GMT-5 + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-03-09T01:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-03-09T02:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeHourlyReturnsFirstMinuteOfHourAfterNextHourDstEnd() { + // America/Chicago 2014 - DST end - Nov 2 02:00 + // during summer GMT-5 + // during winter GMT-6 + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-11-02T01:31:59-05:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + // expect 1h 29min since initial + final Instant expected = + OffsetDateTime.parse("2014-11-02T03:00:00-05:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeHourlyReturnsFirstMinuteOfNextYear() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); + final Instant initial = parseLocalDateTime("2015-12-31T23:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2016-01-01T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMillisecondlyReturnsNextMillisec() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss.SSS}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:53.123"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-04T10:31:53.124"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMinutelyReturnsFirstSecondOfNextMinute() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-04T10:32:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); + final Instant initial = parseLocalDateTime("2014-10-15T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-11-01T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth2() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); + final Instant initial = parseLocalDateTime("2014-01-31T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + // Expect 1st of next month: 2014 Feb 1st + final Instant expected = parseLocalDateTime("2014-02-01T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth3() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); + final Instant initial = parseLocalDateTime("2014-12-31T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + // Expect 1st of next month: 2015 Jan 1st + final Instant expected = parseLocalDateTime("2015-01-01T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeMonthlyReturnsFirstDayOfNextYear() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); + final Instant initial = parseLocalDateTime("2015-12-28T00:00:00"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + // We expect 1st day of next month + final Instant expected = parseLocalDateTime("2016-01-01T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeSecondlyReturnsFirstMillisecOfNextSecond() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:53.123"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-04T10:31:54"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(Resources.LOCALE) + void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_FRANCE() { + final Locale old = Locale.getDefault(); + Locale.setDefault(Locale.FRANCE); // force 1st day of the week to be Monday + + try { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-W}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-10T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + Locale.setDefault(old); + } + } + + @Test + @ResourceLock(Resources.LOCALE) + void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_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/app-%d{yyyy-MM-W}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T10:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-09T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + Locale.setDefault(old); + } + } + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1232 + */ + @Test + @ResourceLock(Resources.LOCALE) + 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 Instant initial = parseLocalDateTime("2015-12-28T00:00:00"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2016-01-03T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + Locale.setDefault(old); + } + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDay() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}.log.gz"); + final Instant initial = parseLocalDateTime("2014-03-04T02:31:59"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = parseLocalDateTime("2014-03-05T00:00:00"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption1() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-03-05T00:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + @ResourceLock(value = Resources.TIME_ZONE) + void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption2() { + final TimeZone old = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("GMT+10")); // default is ignored if pattern contains timezone + try { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-03-05T00:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + TimeZone.setDefault(old); + } + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + @ResourceLock(value = Resources.TIME_ZONE) + void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption3() { + final TimeZone old = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored if pattern contains timezone + try { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-03-05T00:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + TimeZone.setDefault(old); + } + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJan() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-01-04T00:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-01-05T00:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJun() { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-06-04T00:31:59-05:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-06-05T00:00:00-05:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDayDstStart() { + // America/Chicago 2014 - DST start - Mar 9 02:00 + // during winter GMT-6 + // during summer GMT-5 + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-03-09T00:31:59-06:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-03-10T00:00:00-05:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + void testGetNextTimeDailyReturnsFirstHourOfNextDayDstEnd() { + // America/Chicago 2014 - DST end - Nov 2 02:00 + // during summer GMT-5 + // during winter GMT-6 + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz"); + final Instant initial = + OffsetDateTime.parse("2014-11-02T00:31:59-05:00").toInstant(); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = + OffsetDateTime.parse("2014-11-03T00:00:00-06:00").toInstant(); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } + + @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) + @ResourceLock(value = Resources.TIME_ZONE) + void testGetNextTimeDailyReturnsFirstHourOfNextDayInGmtIfZoneIsInvalid() { + final TimeZone old = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored even if timezone option invalid + try { + final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{NOTVALID}.log.gz"); + final Instant initial = Instant.parse("2014-03-04T02:31:59Z"); + final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false); + + final Instant expected = Instant.parse("2014-03-05T00:00:00Z"); + assertEquals(expected, Instant.ofEpochMilli(actual)); + } finally { + TimeZone.setDefault(old); + } + } + + @ParameterizedTest + @ValueSource(strings = {"%d{UNIX}", "%d{UNIX_MILLIS}"}) + void does_not_throw_with_unix_pattern(final String pattern) { + assertDoesNotThrow(() -> new PatternProcessor(pattern)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..6ed7c13e945 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.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 static org.junit.Assert.assertEquals; + +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import org.apache.logging.log4j.Logger; +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.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * + */ +@RunWith(Parameterized.class) +public class RandomRollingAppenderOnStartupTest { + + private static final String DIR = "target/onStartup"; + + private Logger logger; + + @Parameterized.Parameters(name = "{0} \u2192 {1}") + public static Collection data() { + return Arrays.asList(new Object[][] { // + // @formatter:off + {"log4j-test5.xml"}, {"log4j-test5.xml"}, + }); + // @formatter:on + } + + @Rule + public LoggerContextRule loggerContextRule; + + public RandomRollingAppenderOnStartupTest(final String configFile) { + this.loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(configFile); + } + + @Before + public void setUp() { + this.logger = this.loggerContextRule.getLogger(RandomRollingAppenderOnStartupTest.class.getName()); + } + + @BeforeClass + public static void beforeClass() throws Exception { + if (Files.exists(Paths.get("target/onStartup"))) { + try (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + Files.delete(Paths.get(DIR)); + } + } + } + + @AfterClass + public static void afterClass() throws Exception { + long size = 0; + try (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + if (size == 0) { + size = Files.size(path); + } else { + final long fileSize = Files.size(path); + assertEquals( + "Expected size: " + size + " Size of " + path.getFileName() + ": " + fileSize, + size, + fileSize); + } + Files.delete(path); + } + Files.delete(Paths.get(DIR)); + } + } + + @Test + public void testAppender() { + for (int i = 0; i < 100; ++i) { + logger.debug("This is test message number " + i); + } + } +} 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..e8bd5a9e8aa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.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 static org.junit.Assert.assertEquals; + +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.Objects; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +/** + * Validate rolling with a file pattern that contains leading zeros for the increment. + */ +@Ignore("https://issues.apache.org/jira/browse/LOG4J2-3522") +public class RollingAppenderCountTest { + + 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 Logger logger; + + @Rule + public LoggerContextRule loggerContextRule; + + public RollingAppenderCountTest() { + this.loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + } + + @Before + public void setUp() { + this.logger = this.loggerContextRule.getLogger("LogTest"); + } + + @BeforeClass + public static void beforeClass() throws Exception { + if (Files.exists(Paths.get(DIR))) { + try (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + Files.delete(Paths.get(DIR)); + } + } + final File dir = new File(DIR); + if (!dir.exists()) { + Files.createDirectory(new File(DIR).toPath()); + } + final Path target = Paths.get(DIR, TARGET + System.currentTimeMillis()); + Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES); + } + + @AfterClass + public static void afterClass() throws Exception { + final int count = Objects.requireNonNull(new File(DIR).listFiles()).length; + assertEquals("Expected 17 files, got " + count, 17, count); + try (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + } + Files.delete(Paths.get(DIR)); + } + + @Test + public void testLog() throws Exception { + for (long i = 0; i < 60; ++i) { + logger.info("Sequence: " + i); + logger.debug(RandomStringUtils.randomAscii(128, 512)); + Thread.sleep(250); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeLookupTest.java new file mode 100644 index 00000000000..509cf831227 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeLookupTest.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.appender.rolling; + +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.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Arrays; +import java.util.Random; +import org.apache.logging.log4j.Logger; +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; + +/** + * LOG4J2-1804. + */ +public class RollingAppenderCronAndSizeLookupTest { + + private static final String CONFIG = "log4j-rolling-cron-and-size-lookup.xml"; + + private static final String DIR = "target/rolling-cron-size-lookup"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderCronAndSizeLookupTest.class.getName()); + } + + @Test + public void testAppender() throws Exception { + final Random rand = new Random(); + // Loop for 500 times with a 5ms wait guarantees at least 2 time based rollovers. + for (int j = 0; j < 500; ++j) { + for (int i = 0; i < 10; ++i) { + logger.debug("This is test message number " + i); + } + Thread.sleep(5); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists() && dir.listFiles().length > 0); + final File[] files = dir.listFiles(); + Arrays.sort(files); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log")))))); + final int found = 0; + final int fileCounter = 0; + String previous = ""; + for (final File file : files) { + final String actual = file.getName(); + if (previous.isEmpty()) { + previous = actual; + } else { + assertNotSame("File names snould not be equal", previous, actual); + } + } + } +} 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..973ae70a85f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.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 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.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Arrays; +import java.util.Random; +import org.apache.logging.log4j.Logger; +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; + +/** + * LOG4J2-1804. + */ +public class RollingAppenderCronAndSizeTest { + + private static final String CONFIG = "log4j-rolling-cron-and-size.xml"; + + private static final String DIR = "target/rolling-cron-size"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderCronAndSizeTest.class.getName()); + } + + @Test + public void testAppender() throws Exception { + final Random rand = new Random(); + for (int j = 0; j < 100; ++j) { + final int count = rand.nextInt(100); + 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("Directory not created", dir.exists() && dir.listFiles().length > 0); + final File[] files = dir.listFiles(); + Arrays.sort(files); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log")))))); + final int found = 0; + int fileCounter = 0; + String previous = ""; + for (final File file : files) { + final String actual = file.getName(); + final StringBuilder padding = new StringBuilder(); + final String length = Long.toString(file.length()); + for (int i = length.length(); i < 10; ++i) { + padding.append(" "); + } + final String[] fileParts = actual.split("_|\\."); + fileCounter = previous.equals(fileParts[1]) ? ++fileCounter : 1; + previous = fileParts[1]; + assertEquals( + "Incorrect file name. Expected counter value of " + fileCounter + " in " + actual, + Integer.toString(fileCounter), + fileParts[2]); + } + } +} diff --git a/log4j-core/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 similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2DirectTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2DirectTest.java index 5fb4309ed8f..c75f06bc465 100644 --- a/log4j-core/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 @@ -1,37 +1,37 @@ /* * 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.logging.log4j.core.appender.rolling; +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; + import java.io.File; import java.security.SecureRandom; import java.util.Random; - 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.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; /** * @@ -42,7 +42,8 @@ public class RollingAppenderCronEvery2DirectTest { private static final String DIR = "target/rolling-cron-every2Direct"; private static final int LOOP_COUNT = 100; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -82,5 +83,4 @@ public void testAppender() throws Exception { fail("No compressed files found"); } } - } diff --git a/log4j-core/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 similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2Test.java index 228b97b5a9b..9145448d1df 100644 --- a/log4j-core/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 @@ -1,36 +1,37 @@ /* * 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.logging.log4j.core.appender.rolling; +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; + import java.io.File; import java.security.SecureRandom; import java.util.Random; - 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.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.*; /** * @@ -42,7 +43,8 @@ public class RollingAppenderCronEvery2Test { private static final String FILE = "target/rolling-cron-every2/rollingtest.log"; private static final int LOOP_COUNT = 100; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -84,5 +86,4 @@ public void testAppender() throws Exception { fail("No compressed files found"); } } - } 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..822d756034e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnStartupTest.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.rolling; + +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.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.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * + */ +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 + static void after() { + final File dir = new File(DIR); + if (dir.exists()) { + cleanDir(dir); + dir.delete(); + } + } + + @AfterEach + void afterEach() { + if (context != null) { + context.stop(); + } + } + + @Test + void testAppender() throws Exception { + final File dir = new File(DIR); + if (dir.exists()) { + cleanDir(dir); + } else { + dir.mkdirs(); + } + final File file = new File(FILE); + final 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(file.exists(), "Log file does not exist"); + assertTrue(rolled.exists(), "Log file does not exist"); + final LoggerContext lc = Configurator.initialize("Test", CONFIG); + final Logger logger = lc.getLogger(RollingAppenderCronOnStartupTest.class); + logger.info("This is line 3"); + final File[] files = dir.listFiles(); + assertNotNull(files, "No files"); + assertEquals(2, files.length, "Unexpected number of files. Expected 2 but found " + files.length); + List lines = Files.readAllLines(file.toPath()); + assertEquals(2, lines.size(), "Unexpected number of lines. Expected 2: Actual: " + lines.size()); + lines = Files.readAllLines(rolled.toPath()); + assertEquals(1, lines.size(), "Unexpected number of lines. Expected 1: Actual: " + lines.size()); + } + + private static void cleanDir(final File dir) { + final 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..bab06818acc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnceADayTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Calendar; +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.CronExpression; +import org.apache.logging.log4j.status.StatusLogger; +import org.hamcrest.Matcher; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * This test currently takes about a minute to run. + */ +public class RollingAppenderCronOnceADayTest { + + private static final int CRON_DELAY = 10; + private static final String UTF_8 = "UTF-8"; + 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 long remainingTime; + + @BeforeClass + public static void beforeClass() throws Exception { + final Path src = FileSystems.getDefault().getPath(TARGET_TEST_CLASSES, CONFIG); + String content = new String(Files.readAllBytes(src), StandardCharsets.UTF_8); + final Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND, CRON_DELAY); + remainingTime = cal.getTimeInMillis() - System.currentTimeMillis(); + cronExpression = String.format( + "%d %d %d * * ?", cal.get(Calendar.SECOND), cal.get(Calendar.MINUTE), cal.get(Calendar.HOUR_OF_DAY)); + content = content.replace("@CRON_EXPR@", cronExpression); + Files.write( + FileSystems.getDefault().getPath(TARGET_TEST_CLASSES, CONFIG_TARGET), + content.getBytes(StandardCharsets.UTF_8)); + StatusLogger.getLogger().debug("Cron expression will be " + cronExpression + " in " + remainingTime + "ms"); + } + + private final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG_TARGET); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + @Test + public void testAppender() throws Exception { + // TODO Is there a better way to test than putting the thread to sleep all over the place? + final Logger logger = loggerContextRule.getLogger(); + final File file = new File(FILE); + assertTrue("Log file does not exist", file.exists()); + logger.debug("This is test message number 1, waiting for rolling"); + + final RollingFileAppender app = + loggerContextRule.getLoggerContext().getConfiguration().getAppender("RollingFile"); + final TriggeringPolicy policy = app.getManager().getTriggeringPolicy(); + assertNotNull("No triggering policy", policy); + assertTrue("Incorrect policy type", policy instanceof CronTriggeringPolicy); + final CronExpression expression = ((CronTriggeringPolicy) policy).getCronExpression(); + assertEquals("Incorrect cron expresion", cronExpression, expression.getCronExpression()); + logger.debug("Cron expression will be {}", expression.getCronExpression()); + + // force a reconfiguration + for (int i = 1; i <= 20; ++i) { + logger.debug("Adding first event {}", i); + } + + Thread.sleep(remainingTime); + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists() && dir.listFiles().length > 0); + + for (int i = 1; i < 5; i++) { + logger.debug("Adding some more event {}", i); + Thread.sleep(1000); + } + final Matcher hasGzippedFile = hasName(that(endsWith(".gz"))); + int count = 0; + final File[] files = dir.listFiles(); + for (final File generatedFile : files) { + if (hasGzippedFile.matches(generatedFile)) { + count++; + } + } + + assertNotEquals("No compressed files found", 0, count); + assertEquals("Multiple files found", 1, count); + } +} diff --git a/log4j-core/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 similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronTest.java index d6e31feca4c..0130de1e481 100644 --- a/log4j-core/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 @@ -1,25 +1,26 @@ /* * 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.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.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -30,11 +31,10 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; - 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.CronExpression; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; @@ -49,7 +49,8 @@ public class RollingAppenderCronTest { private static final String DIR = "target/rolling-cron"; private static final String FILE = "target/rolling-cron/rollingtest.log"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -94,13 +95,12 @@ public void testAppender() throws Exception { logger.debug("Adding new event {}", i); } Thread.sleep(1000); - final RollingFileAppender app = (RollingFileAppender) loggerContextRule.getLoggerContext().getConfiguration().getAppender("RollingFile"); + final RollingFileAppender app = + loggerContextRule.getLoggerContext().getConfiguration().getAppender("RollingFile"); final TriggeringPolicy policy = app.getManager().getTriggeringPolicy(); assertNotNull("No triggering policy", policy); assertTrue("Incorrect policy type", policy instanceof CronTriggeringPolicy); final CronExpression expression = ((CronTriggeringPolicy) policy).getCronExpression(); - assertTrue("Incorrect triggering policy", expression.getCronExpression().equals("* * * ? * *")); - + assertEquals("Incorrect triggering policy", "* * * ? * *", expression.getCronExpression()); } - } diff --git a/log4j-core/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 similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCustomDeleteActionTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCustomDeleteActionTest.java index 3b30bd8e884..a5a015f35bf 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -22,9 +22,8 @@ import java.io.File; import java.util.Arrays; import java.util.regex.Pattern; - 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.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -37,7 +36,8 @@ public class RollingAppenderCustomDeleteActionTest { private static final String CONFIG = "log4j-rolling-with-custom-delete.xml"; private static final String DIR = "target/rolling-with-delete/test"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -64,8 +64,10 @@ public void testAppender() throws Exception { } if (files.length == 3) { for (final File file : files) { - assertTrue("test-4.log should have been deleted", - Arrays.asList("test-1.log", "test-2.log", "test-3.log").contains(file.getName())); + assertTrue( + "test-4.log should have been deleted", + Arrays.asList("test-1.log", "test-2.log", "test-3.log") + .contains(file.getName())); } return; // test succeeded } diff --git a/log4j-core/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 similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount1Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount1Test.java index 23dcffa85ac..3e375275457 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -30,11 +30,10 @@ import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.List; - import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -47,7 +46,8 @@ 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"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -96,7 +96,7 @@ private void updateLastModified(final Path... paths) throws IOException { 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())) { + try (final BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { buffy.write("some text"); buffy.newLine(); buffy.flush(); diff --git a/log4j-core/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 similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount2Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount2Test.java index f6362752f85..8329c6ef518 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -30,11 +30,10 @@ import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.List; - import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -47,7 +46,8 @@ 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"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -97,7 +97,7 @@ private void updateLastModified(final Path... paths) throws IOException { 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())) { + try (final BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { buffy.write("some text"); buffy.newLine(); buffy.flush(); diff --git a/log4j-core/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 similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedSizeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedSizeTest.java index f580b79d95c..5e36333f56e 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -21,11 +21,10 @@ import java.io.File; import java.util.Arrays; - import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -37,7 +36,8 @@ 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"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); diff --git a/log4j-core/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 similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteMaxDepthTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteMaxDepthTest.java index 740f9ecfd1c..f51a7c9e2b8 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -29,9 +29,8 @@ import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; - 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.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -44,7 +43,8 @@ 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"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -72,8 +72,7 @@ public void testAppender() throws Exception { final List expected = Arrays.asList("1", "2", "test-1.log", "test-2.log", "test-3.log"); assertEquals(Arrays.toString(files), expected.size(), files.length); for (final File file : files) { - assertTrue("test-4.log should have been deleted", - expected.contains(file.getName())); + assertTrue("test-4.log should have been deleted", expected.contains(file.getName())); } assertTrue(p1 + " should not have been deleted", Files.exists(p1)); @@ -85,7 +84,7 @@ public void testAppender() throws Exception { 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())) { + try (final BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { buffy.write("some text"); buffy.newLine(); buffy.flush(); diff --git a/log4j-core/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 similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteNestedTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteNestedTest.java index 7d335f03e13..c9691087ff4 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -30,11 +30,10 @@ import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.List; - import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -46,7 +45,8 @@ 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"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); @@ -77,7 +77,7 @@ public void testAppender() throws Exception { System.out.println(file + " (" + file.length() + "B) " + FixedDateFormat.create(FixedFormat.ABSOLUTE).format(file.lastModified())); } - + final List expected = Arrays.asList("my-1.log", "my-2.log", "my-3.log", "my-4.log", "my-5.log"); assertEquals(Arrays.toString(files), expected.size() + 3, files.length); for (final File file : files) { @@ -96,7 +96,7 @@ private void updateLastModified(final Path... paths) throws IOException { 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())) { + try (final BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { buffy.write("some text"); buffy.newLine(); buffy.flush(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptFri13thTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptFri13thTest.java new file mode 100644 index 00000000000..49210e73aea --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptFri13thTest.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.appender.rolling; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; + +import java.io.File; +import java.time.DayOfWeek; +import java.time.LocalDate; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class RollingAppenderDeleteScriptFri13thTest { + + private static final String CONFIG = "log4j-rolling-with-custom-delete-script-fri13th.xml"; + private static final String DIR = "target/rolling-with-delete-script-fri13th/test"; + + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.SCRIPT_LANGUAGES, "Groovy, Javascript"); + } + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + @Test + public void testAppender() throws Exception { + LocalDate now = LocalDate.now(); + // Ignore on Friday 13th + assumeFalse(now.getDayOfWeek() == DayOfWeek.FRIDAY && now.getDayOfMonth() == 13); + final File dir = new File(DIR); + dir.mkdirs(); + for (int i = 1; i <= 30; i++) { + final String day = i < 10 ? "0" + i : "" + i; + new File(dir, "test-201511" + day + "-0.log").createNewFile(); + } + assertEquals("Dir " + DIR + " filecount", 30, dir.listFiles().length); + + final Logger logger = loggerContextRule.getLogger(); + // Trigger the rollover + while (dir.listFiles().length < 32) { + // 60+ chars per message: each message should trigger a rollover + logger.debug("This is a very, very, very, very long test message............."); // 60+ chars: + Thread.sleep(100); // Allow time for rollover to complete + } + + final File[] files = dir.listFiles(); + for (final File file : files) { + System.out.println(file); + } + for (final File file : files) { + assertTrue(file.getName() + " starts with 'test-'", file.getName().startsWith("test-")); + assertTrue(file.getName() + " ends with '.log'", file.getName().endsWith(".log")); + final String strDate = file.getName().substring(5, 13); + assertFalse(file + " is not Fri 13th", strDate.endsWith("20151113")); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptTest.java new file mode 100644 index 00000000000..c65c2114b30 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteScriptTest.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.appender.rolling; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.Integers; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * + */ +public class RollingAppenderDeleteScriptTest { + + private static final String CONFIG = "log4j-rolling-with-custom-delete-script.xml"; + private static final String DIR = "target/rolling-with-delete-script/test"; + + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.SCRIPT_LANGUAGES, "Groovy, Javascript"); + } + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + @Test + public void testAppender() throws Exception { + final Logger logger = loggerContextRule.getLogger(); + // 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 " + DIR + " should exist", dir.exists()); + assertTrue("Dir " + DIR + " should contain files", dir.listFiles().length > 0); + + final File[] files = dir.listFiles(); + for (final File file : files) { + System.out.println(file); + } + for (final File file : files) { + assertTrue(file.getName() + " starts with 'test-'", file.getName().startsWith("test-")); + assertTrue(file.getName() + " ends with '.log'", file.getName().endsWith(".log")); + final String strIndex = file.getName().substring(5, file.getName().indexOf('.')); + final int index = Integers.parseInt(strIndex); + assertEquals(file + " should have odd index", 1, index % 2); + } + } +} 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..6751d3cb1f6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCronTest.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.appender.rolling; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +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.test.junit.Named; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +class RollingAppenderDirectCronTest { + + private static final Pattern FILE_PATTERN = + Pattern.compile("test-(\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2})\\.log"); + + @TempLoggingDir + private static Path loggingPath; + + @Test + @LoggerContextSource("classpath:appender/rolling/RollingAppenderDirectCronTest.xml") + void testAppender(final LoggerContext ctx, @Named("RollingFile") final RollingFileAppender app) throws Exception { + final Logger logger = ctx.getLogger(RollingAppenderDirectCronTest.class); + int msgNumber = 1; + logger.debug("This is test message number {}.", msgNumber++); + assertThat(loggingPath).isNotEmptyDirectory(); + final RolloverDelay delay = new RolloverDelay(app.getManager()); + delay.waitForRollover(); + + delay.reset(3); + final int MAX_TRIES = 30; + for (int i = 0; i < MAX_TRIES; ++i) { + logger.debug("This is test message number {}.", msgNumber++); + Thread.sleep(110); + } + delay.waitForRollover(); + } + + private static class RolloverDelay implements RolloverListener { + private final Logger logger = StatusLogger.getLogger(); + private volatile CountDownLatch latch; + private volatile AssertionError assertion; + + public RolloverDelay(final RollingFileManager manager) { + latch = new CountDownLatch(1); + manager.addRolloverListener(this); + } + + public void waitForRollover() { + waitAtMost(5, TimeUnit.SECONDS) + .alias("Rollover timeout") + .until(() -> latch.getCount() == 0 || assertion != null); + if (assertion != null) { + throw assertion; + } + } + + public void reset(final int count) { + latch = new CountDownLatch(count); + } + + @Override + public void rolloverTriggered(final String fileName) { + logger.info("Rollover triggered for file {}.", fileName); + } + + @Override + public void rolloverComplete(final String fileName) { + logger.info("Rollover completed for file {}.", fileName); + try { + final Path path = Paths.get(fileName); + final Matcher matcher = FILE_PATTERN.matcher(path.getFileName().toString()); + assertThat(matcher).as("Rolled file").matches(); + try { + final List lines = Files.readAllLines(path); + assertThat(lines).isNotEmpty(); + assertThat(lines.get(0)).startsWith(matcher.group(1)); + } catch (final IOException ex) { + fail("Unable to read file " + fileName + ": " + ex.getMessage()); + } + latch.countDown(); + } catch (final AssertionError ex) { + assertion = ex; + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCustomDeleteActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCustomDeleteActionTest.java new file mode 100644 index 00000000000..4c812d4daf5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCustomDeleteActionTest.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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.util.Arrays; +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; + +/** + * + */ +public class RollingAppenderDirectCustomDeleteActionTest implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-direct-with-custom-delete.xml"; + private static final String DIR = "target/rolling-direct-with-delete/test"; + + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private boolean fileFound = false; + + @Test + public void testAppender() throws Exception { + final Logger logger = loggerContextRule.getLogger(); + final RollingFileAppender app = loggerContextRule.getAppender("RollingFile"); + assertNotNull(app, "No RollingFileAppender"); + app.getManager().addRolloverListener(this); + // 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 " + DIR + " should exist", dir.exists()); + assertTrue("Dir " + DIR + " should contain files", dir.listFiles().length > 0); + + final File[] files = dir.listFiles(); + assertNotNull(files, "No fiels"); + System.out.println(files[0].getName()); + final long count = Arrays.stream(files) + .filter((f) -> f.getName().endsWith("test-4.log")) + .count(); + assertTrue("Deleted file was not created", fileFound); + assertEquals("File count expected: 0, actual: " + count, 0, count); + } + + 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); + } + } + + @Override + public void rolloverTriggered(final String fileName) { + if (fileName.endsWith("test-4.log")) { + fileFound = true; + } + } + + @Override + public void rolloverComplete(final String fileName) {} +} 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..dab76cab14a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.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.logging.log4j.core.appender.rolling; + +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.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.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * + */ +public class RollingAppenderDirectWrite1906Test { + + private static final String CONFIG = "log4j-rolling-direct-1906.xml"; + + private static final String DIR = "target/rolling-direct-1906"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @BeforeClass + public static void setupClass() { + StatusLogger.getLogger().registerListener(new NoopStatusListener()); + } + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderDirectWrite1906Test.class.getName()); + } + + @Test + public void testAppender() throws Exception { + final int count = 100; + for (int i = 0; i < count; ++i) { + logger.debug("This is test message number " + i); + Thread.sleep(50); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists() && dir.listFiles().length > 0); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log")))))); + int found = 0; + for (final File file : files) { + final String actual = file.getName(); + final BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + assertNotNull("No log event in file " + actual, line); + final String[] parts = line.split((" ")); + final String expected = "rollingfile." + parts[0] + ".log"; + + assertEquals(logFileNameError(expected, actual), expected, actual); + ++found; + } + reader.close(); + } + assertEquals("Incorrect number of events read. Expected " + count + ", Actual " + found, count, found); + } + + private String logFileNameError(final String expected, final String actual) { + final List statusData = StatusLogger.getLogger().getStatusData(); + final StringBuilder sb = new StringBuilder(); + for (StatusData statusItem : statusData) { + sb.append(statusItem.getFormattedStatus()); + sb.append("\n"); + } + sb.append("Incorrect file name. Expected: ") + .append(expected) + .append(" Actual: ") + .append(actual); + return sb.toString(); + } + + private static class NoopStatusListener implements StatusListener { + @Override + public void log(final StatusData data) {} + + @Override + public Level getStatusLevel() { + return Level.TRACE; + } + + @Override + public void close() {} + } +} 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..47ab9404efd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.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.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.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 { + final 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() { + 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..087fb7aefa4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.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 static org.assertj.core.api.Assertions.assertThat; + +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.concurrent.TimeUnit; +import java.util.regex.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.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * LOG4J2-1766. + */ +@UsingStatusListener +@DisabledOnOs(value = OS.MAC, disabledReason = "FileWatcher is not fast enough on macOS for this test") +class RollingAppenderDirectWriteTempCompressedFilePatternTest { + + private final String PATTERN = "test-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d+\\.log\\.gz"; + private final Pattern FILE_PATTERN = Pattern.compile(PATTERN); + private final Pattern TMP_PATTERN = Pattern.compile(PATTERN + "\\.tmp"); + + @TempLoggingDir + private Path loggingPath; + + @Test + @LoggerContextSource + void testAppender(final LoggerContext ctx) throws Exception { + final Logger logger = ctx.getLogger(getClass()); + try (final WatchService watcher = FileSystems.getDefault().newWatchService()) { + WatchKey key = loggingPath.register(watcher, StandardWatchEventKinds.ENTRY_CREATE); + + for (int i = 0; i < 100; ++i) { + logger.debug("This is test message number {}.", i); + } + ctx.stop(500, TimeUnit.MILLISECONDS); + + int temporaryFilesCreated = 0; + int compressedFiles = 0; + key = watcher.take(); + + for (final WatchEvent event : key.pollEvents()) { + final WatchEvent ev = (WatchEvent) event; + final String filename = ev.context().getFileName().toString(); + if (TMP_PATTERN.matcher(filename).matches()) { + temporaryFilesCreated++; + } + if (FILE_PATTERN.matcher(filename).matches()) { + compressedFiles++; + } + } + assertThat(temporaryFilesCreated) + .as("Temporary files created.") + .isGreaterThan(0) + .isEqualTo(compressedFiles); + } + } +} 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..f28b6268cb8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.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.appender.rolling; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; +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.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +class RollingAppenderDirectWriteTest { + + private final Pattern FILE_PATTERN = Pattern.compile("test-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d+\\.log(\\.gz)?"); + private final Pattern LINE_PATTERN = Pattern.compile("This is test message number \\d+\\."); + + @TempLoggingDir + private Path loggingPath; + + @Test + @LoggerContextSource + void testAppender(final LoggerContext ctx) throws Exception { + final Logger logger = ctx.getLogger(getClass()); + final int count = 100; + for (int i = 0; i < count; ++i) { + logger.debug("This is test message number {}.", i); + } + ctx.stop(500, TimeUnit.MILLISECONDS); + int found = 0; + try (final DirectoryStream stream = Files.newDirectoryStream(loggingPath)) { + for (final Path file : stream) { + final String fileName = file.getFileName().toString(); + assertThat(fileName).matches(FILE_PATTERN); + try (final InputStream is = Files.newInputStream(file); + final InputStream uncompressed = fileName.endsWith(".gz") ? new GZIPInputStream(is) : is; + final BufferedReader reader = new BufferedReader(new InputStreamReader(uncompressed, UTF_8))) { + String line; + int lineIndex = 0; + while ((line = reader.readLine()) != null) { + assertThat(line) + .as("line %d of file `%s`", ++lineIndex, file) + .matches(LINE_PATTERN); + ++found; + } + } + } + } + + assertThat(found).as("Number of events.").isEqualTo(count); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..530c65c1fbd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.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.appender.rolling; + +import static org.junit.Assert.assertFalse; + +import java.io.File; +import org.apache.logging.log4j.Logger; +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; + +/** + * + */ +public class RollingAppenderDirectWriteWithFilenameTest { + + private static final String CONFIG = "log4j2-rolling-1833.xml"; + + private static final String DIR = "target/rolling-1833"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderDirectWriteWithFilenameTest.class.getName()); + } + + @Test + public void testAppender() { + final File dir = new File(DIR); + assertFalse("Directory created", dir.exists()); + } +} 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..82d3c118eef --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.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.appender.rolling; + +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.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.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +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.LoggerContextRule; +import org.apache.logging.log4j.core.util.IOUtils; +import org.apache.logging.log4j.message.SimpleMessage; +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * Tests for LOG4J2-2760 + */ +public class RollingAppenderDirectWriteWithHtmlLayoutTest { + + private static final String DIR = "target/rolling-direct-htmlLayout"; + + public static LoggerContextRule loggerContextRule = new LoggerContextRule(); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + @Test + public void testRollingFileAppenderWithHtmlLayout() throws Exception { + checkAppenderWithHtmlLayout(true); + } + + @Test + public void testRollingFileAppenderWithHtmlLayoutNoAppend() throws Exception { + checkAppenderWithHtmlLayout(false); + } + + private void checkAppenderWithHtmlLayout(final boolean append) throws InterruptedException, IOException { + final String prefix = "testHtml_" + (append ? "append_" : "noAppend_"); + final Configuration config = loggerContextRule.getConfiguration(); + final 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 { + final 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("Directory not created", dir.exists()); + 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 (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + final 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("Incorrect number of events read.", count, foundEvents); + } 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..f27ca883d3c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithReconfigureTest.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.appender.rolling; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.net.URI; +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.junit.LoggerContextRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * + */ +public class RollingAppenderDirectWriteWithReconfigureTest { + + private static final String CONFIG = "log4j-rolling-direct-reconfigure.xml"; + + private static final String DIR = "target/rolling-direct-reconfigure"; + + private static final int MAX_TRIES = 10; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderDirectWriteWithReconfigureTest.class.getName()); + } + + @Test + public void testRollingFileAppenderWithReconfigure() throws Exception { + logger.debug("Before reconfigure"); + + @SuppressWarnings("resource") // managed by the rule. + final LoggerContext context = loggerContextRule.getLoggerContext(); + final Configuration config = context.getConfiguration(); + context.setConfigLocation(new URI(CONFIG)); + context.reconfigure(); + logger.debug("Force a rollover"); + final File dir = new File(DIR); + for (int i = 0; i < MAX_TRIES; ++i) { + Thread.sleep(200); + if (config != context.getConfiguration()) { + break; + } + } + + assertTrue("Directory not created", dir.exists() && dir.listFiles().length > 0); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(dir.listFiles().length, is(equalTo(2))); + } +} 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 76% 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..803a898c20e 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 @@ -1,38 +1,38 @@ /* * 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.logging.log4j.core.appender.rolling; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + 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.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.*; - /** * */ @@ -45,11 +45,11 @@ public class RollingAppenderNoUnconditionalDeleteTest { @Parameterized.Parameters(name = "{0} \u2192 {1}") public static Collection data() { return Arrays.asList(new Object[][] { // - // @formatter:off - {"log4j-rolling-with-custom-delete-unconditional1.xml", "target/rolling-unconditional-delete1/test"}, // - {"log4j-rolling-with-custom-delete-unconditional2.xml", "target/rolling-unconditional-delete2/test"}, // - {"log4j-rolling-with-custom-delete-unconditional3.xml", "target/rolling-unconditional-delete3/test"}, // - // @formatter:on + // @formatter:off + {"log4j-rolling-with-custom-delete-unconditional1.xml", "target/rolling-unconditional-delete1/test"}, // + {"log4j-rolling-with-custom-delete-unconditional2.xml", "target/rolling-unconditional-delete2/test"}, // + {"log4j-rolling-with-custom-delete-unconditional3.xml", "target/rolling-unconditional-delete3/test"}, // + // @formatter:on }); } @@ -64,7 +64,7 @@ public RollingAppenderNoUnconditionalDeleteTest(final String configFile, final S } @Before - public void setUp() throws Exception { + public void setUp() { this.logger = this.loggerContextRule.getLogger(); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartup2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartup2Test.java new file mode 100644 index 00000000000..7dba6fcfddf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartup2Test.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.logging.log4j.core.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.util.datetime.FastDateFormat; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * + */ +class RollingAppenderOnStartup2Test { + + private static final String DIR = "target/rollOnStartup"; + private static final String TARGET_FILE = DIR + "/orchestrator.log"; + private static final FastDateFormat formatter = FastDateFormat.getInstance("MM-dd-yy-HH-mm-ss"); + private static final String ROLLED_FILE_PREFIX = DIR + "/orchestrator-"; + private static final String ROLLED_FILE_SUFFIX = "-1.log.gz"; + private static final String TEST_DATA = "Hello world!"; + + @BeforeAll + static void beforeClass() throws Exception { + if (Files.exists(Paths.get("target/rollOnStartup"))) { + try (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + Files.delete(Paths.get(DIR)); + } + } + // System.setProperty("log4j2.debug", "true"); + // System.setProperty("log4j2.StatusLogger.level", "trace"); + final Configuration configuration = new DefaultConfiguration(); + final Path target = Paths.get(TARGET_FILE); + assertFalse(Files.exists(target)); + target.toFile().getParentFile().mkdirs(); + final long timeStamp = System.currentTimeMillis() - (1000 * 60 * 60 * 24); + final String expectedDate = formatter.format(timeStamp); + final String rolledFileName = ROLLED_FILE_PREFIX + expectedDate + ROLLED_FILE_SUFFIX; + final Path rolled = Paths.get(rolledFileName); + 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); + System.setProperty("log4j.configurationFile", "log4j-rollOnStartup.json"); + } + + @AfterAll + static void afterClass() { + final long size = 0; + /* try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + if (size == 0) { + size = Files.size(path); + } else { + final long fileSize = Files.size(path); + assertTrue("Expected size: " + size + " Size of " + path.getFileName() + ": " + fileSize, + size == fileSize); + } + Files.delete(path); + } + Files.delete(Paths.get("target/rollOnStartup")); + } */ + } + + @Test + void testAppender() { + final Logger logger = LogManager.getLogger(RollingAppenderOnStartup2Test.class); + for (int i = 0; i < 10; ++i) { + logger.debug("This is test message number " + i); + } + } +} 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..aa7e4a51063 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupDirectTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * + */ +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; + + @BeforeAll + static void beforeClass() throws Exception { + if (Files.exists(Paths.get("target/onStartup"))) { + try (final 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()); + final String fileName = ROLLED + formatter.format(LocalDate.now()) + "-1.log"; + final Path target = Paths.get(DIR, fileName); + Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES); + final FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS)); + Files.getFileAttributeView(target, BasicFileAttributeView.class).setTimes(newTime, newTime, newTime); + } + + @Test + 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 (final DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + ++fileCount; + if (path.toFile().getName().startsWith(ROLLED)) { + final List lines = Files.readAllLines(path); + assertFalse( + lines.isEmpty(), "No messages in " + path.toFile().getName()); + assertTrue( + lines.get(0).startsWith(PREFIX), + "Missing message for " + path.toFile().getName()); + } + } + } + assertEquals(2, fileCount, "File did not roll"); + } + + @AfterAll + static void afterClass() throws Exception { + Configurator.shutdown(loggerContext); + try (final 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..a65513050f0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.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.rolling; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +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.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.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +class RollingAppenderOnStartupTest { + + private static final String SOURCE = "src/test/resources/__files"; + private static final String FILENAME = "onStartup.log"; + private static final String PREFIX = "This is test message number "; + private static final String ROLLED = "onStartup-"; + + @TempLoggingDir + private static Path loggingPath; + + @BeforeAll + static void setup() throws Exception { + final Path target = loggingPath.resolve(FILENAME); + Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.REPLACE_EXISTING); + final FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS)); + final BasicFileAttributeView attrs = Files.getFileAttributeView(target, BasicFileAttributeView.class); + attrs.setTimes(newTime, newTime, newTime); + /* + * POSIX does not define a file creation timestamp. + * Depending on the system `creationTime` might be: + * * 0, + * * the last modification time + * * or the time the file was actually created. + * + * This test fails if the latter occurs, since the file is created after the JVM. + */ + final FileTime creationTime = attrs.readAttributes().creationTime(); + assumeTrue(creationTime.equals(newTime) || creationTime.toMillis() == 0L); + } + + @Test + @LoggerContextSource + 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 (final DirectoryStream directoryStream = Files.newDirectoryStream(loggingPath)) { + for (final Path path : directoryStream) { + if (path.toFile().getName().startsWith(ROLLED)) { + rolled = true; + final List lines = Files.readAllLines(path); + assertFalse( + lines.isEmpty(), "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-test/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 new file mode 100644 index 00000000000..8e6a300a28d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.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.appender.rolling; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import org.apache.commons.io.FileUtils; +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.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +/** + * LOG4J2-1725. + */ +@UsingStatusListener +class RollingAppenderReconfigureTest { + + private static final URL CONFIG = + RollingAppenderReconfigureTest.class.getResource("RollingAppenderReconfigureTest.xml"); + private static final File CONFIG_FILE = FileUtils.toFile(CONFIG); + + @TempLoggingDir + private static Path loggingPath; + + @Test + @LoggerContextSource + void testReconfigure(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 500; ++i) { + logger.debug("This is test message number {}", i); + } + + assertThat(loggingPath).isDirectoryContaining("glob:**/*.current").isDirectoryContaining("glob:**/*.rolled"); + + final String originalXmlConfig = FileUtils.readFileToString(CONFIG_FILE, UTF_8); + try { + final String updatedXmlConfig = + originalXmlConfig.replace("rollingtest.%i.rolled", "rollingtest.%i.reconfigured"); + FileUtils.write(CONFIG_FILE, updatedXmlConfig, UTF_8); + + // Reconfigure + context.reconfigure(); + + for (int i = 0; i < 500; ++i) { + logger.debug("This is test message number {}", i); + } + + assertThat(loggingPath) + .isDirectoryContaining("glob:**/*.reconfigured") + .isDirectoryContaining("glob:**/*.current") + .isDirectoryContaining("glob:**/*.rolled"); + } finally { + FileUtils.write(CONFIG_FILE, originalXmlConfig, UTF_8); + } + } +} 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..87945f909bb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.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 static junit.framework.Assert.fail; +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; +import static org.junit.Assume.assumeTrue; + +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.TimeUnit; +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.rules.RuleChain; + +public class RollingAppenderRestartTest { + + 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 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); + final FileTime newTime = FileTime.from(Instant.now().minus(2, ChronoUnit.DAYS)); + final BasicFileAttributeView attrs = Files.getFileAttributeView(FILE, BasicFileAttributeView.class); + attrs.setTimes(newTime, newTime, newTime); + /* + * POSIX does not define a file creation timestamp. + * Depending on the system `creationTime` might be: + * * 0, + * * the last modification time + * * or the time the file was actually created. + * + * This test fails if the latter occurs, since the file is created after the JVM. + */ + final FileTime creationTime = attrs.readAttributes().creationTime(); + assumeTrue(creationTime.equals(newTime) || creationTime.toMillis() == 0L); + } + + @AfterClass + public static void tearDown() throws IOException { + if (Files.exists(DIR)) { + PathUtils.deleteDirectory(DIR); + } + } + + @Test + public void testAppender() throws Exception { + final Logger logger = loggerContextRule.getLogger(); + logger.info("This is test message number 1"); + // The GZ compression takes place asynchronously. + // Make sure it's done before validating. + Thread.yield(); + final String name = "RollingFile"; + final RollingFileAppender appender = loggerContextRule.getAppender(name); + assertNotNull(name, appender); + if (appender.getManager().getSemaphore().tryAcquire(5, TimeUnit.SECONDS)) { + // If we are in here, either the rollover is done or has not taken place yet. + validate(); + } else { + fail("Rolling over is taking too long."); + } + } + + private void validate() { + final Matcher hasGzippedFile = hasItemInArray(that(hasName(that(endsWith(".gz"))))); + final File[] files = DIR.toFile().listFiles(); + Arrays.sort(files); + assertTrue( + "was expecting files with '.gz' suffix, found: " + Arrays.toString(files), + hasGzippedFile.matches(files)); + } +} 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..98bc0989b7a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeCompressPermissionsTest.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.appender.rolling; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.FileUtils; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * LOG4J2-1699. + */ +public class RollingAppenderSizeCompressPermissionsTest { + + private static final String CONFIG = "log4j-rolling-gz-posix.xml"; + + private static final String DIR = "target/rollingpermissions1"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @BeforeClass + public static void beforeClass() { + Assume.assumeTrue(FileUtils.isFilePosixAttributeViewSupported()); + } + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderSizeCompressPermissionsTest.class.getName()); + } + + @Test + public void testAppenderCompressPermissions() 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); + } + } + if (!loggerContextRule.getLoggerContext().stop(30, TimeUnit.SECONDS)) { + System.err.println("Could not stop cleanly " + loggerContextRule + " for " + this); + } + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists()); + 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 not rolled : " + files.length, files.length > 2); + assertTrue("Files 1 gzipped not rolled : " + gzippedFiles1, gzippedFiles1 > 0); + assertTrue("Files 2 gzipped not rolled : " + gzippedFiles2, gzippedFiles2 > 0); + } +} 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..4b49a59f120 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeMaxWidthTest.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.rolling; + +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 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; + +/** + * + */ +@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(final 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() { + this.logger = loggerContextRule.getLogger(RollingAppenderSizeMaxWidthTest.class.getName()); + final RollingFileAppender app = (RollingFileAppender) loggerContextRule.getRequiredAppender("RollingFile"); + app.getManager().addRolloverListener(this); + final ArrayPatternConverter[] patternConverters = + app.getManager().getPatternProcessor().getPatternConverters(); + final 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()); + } + final FormattingInfo formattingInfo = + app.getManager().getPatternProcessor().getPatternFields()[index]; + minWidth = formattingInfo.getMinLength(); + maxWidth = formattingInfo.getMaxLength(); + isZeroPad = formattingInfo.isZeroPad(); + final 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() { + 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) { + final 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); + final int maxFiles = max - min + 1; + final int maxExpectedFiles = Math.min(maxFiles, rolloverCount); + assertEquals( + "More files than expected. expected: " + maxExpectedFiles + ", actual: " + files.length, + maxExpectedFiles, + files.length); + } + + @Override + public void rolloverTriggered(final String fileName) { + ++rolloverCount; + } + + @Override + public void rolloverComplete(final 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..3906a4c79f6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.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.appender.rolling; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +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.StatusLogger; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +/** + * LOG4J2-1804. + */ +@UsingStatusListener +class RollingAppenderSizeNoCompressTest { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + @TempLoggingDir + private static Path loggingPath; + + @Test + @LoggerContextSource + void testAppender(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(getClass()); + 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 (!context.stop(30, TimeUnit.SECONDS)) { + LOGGER.error("Could not stop cleanly logger context {}.", context); + } + final List files = StreamSupport.stream( + Files.newDirectoryStream(loggingPath).spliterator(), false) + .collect(Collectors.toList()); + for (final Path file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Files.copy(file, baos); + final String text = new String(baos.toByteArray(), Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertThat(messages).as("Lost messages").isEmpty(); + assertThat(files).as("Log files").hasSizeGreaterThan(31); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ea1dbc6e988 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.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.logging.log4j.core.appender.rolling; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.apache.commons.compress.compressors.CompressorStreamFactory; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@UsingStatusListener +class RollingAppenderSizeTest { + + private static long DEFAULT_SHUTDOWN_MS = 500; + + private static final Pattern MESSAGE_PATTERN = Pattern.compile("This is test message numer \\d+."); + + private static final List FILE_EXTENSIONS = + Arrays.asList("gz", "zip", "bz2", "deflate", "pack200", "xz", "zst"); + + static Stream parameters() { + return FILE_EXTENSIONS.stream().flatMap(fileExtension -> { + return Stream.of(Arguments.of(fileExtension, true), Arguments.of(fileExtension, false)); + }); + } + + @TempLoggingDir + private static Path loggingPath; + + private static RollingFileAppender createRollingFileAppender( + final String fileExtension, final boolean createOnDemand) { + final Path folder = loggingPath.resolve(fileExtension); + final String fileName = folder.resolve("rollingtest.log").toString(); + final String filePattern = + folder.resolve("rollingtest-%i.log." + fileExtension).toString(); + final RollingFileAppender appender = RollingFileAppender.newBuilder() + .setName("RollingFile") + .setFileName(fileName) + .setFilePattern(filePattern) + .setLayout(PatternLayout.createDefaultLayout()) + .setPolicy(SizeBasedTriggeringPolicy.createPolicy("500")) + .setCreateOnDemand(createOnDemand) + .build(); + appender.start(); + return appender; + } + + private static LogEvent createEvent(final String pattern, final Object p0) { + return Log4jLogEvent.newBuilder() + .setMessage(new ParameterizedMessage(pattern, p0)) + .build(); + } + + @ParameterizedTest + @MethodSource("parameters") + void testIsCreateOnDemand(final String fileExtension, final boolean createOnDemand) throws IOException { + final Path extensionFolder = loggingPath.resolve(fileExtension); + RollingFileAppender appender = null; + try { + appender = createRollingFileAppender(fileExtension, createOnDemand); + final RollingFileManager manager = appender.getManager(); + assertThat(manager).isNotNull().extracting("createOnDemand").isEqualTo(createOnDemand); + + } finally { + appender.stop(DEFAULT_SHUTDOWN_MS, TimeUnit.MILLISECONDS); + FileUtils.deleteDirectory(extensionFolder.toFile()); + } + } + + @ParameterizedTest + @MethodSource("parameters") + void testAppender(final String fileExtension, final boolean createOnDemand) throws Exception { + final Path extensionFolder = loggingPath.resolve(fileExtension); + RollingFileAppender appender = null; + try { + appender = createRollingFileAppender(fileExtension, createOnDemand); + final Path currentLog = extensionFolder.resolve("rollingtest.log"); + if (createOnDemand) { + assertThat(currentLog).as("file created on demand").doesNotExist(); + } + for (int i = 0; i < 500; ++i) { + appender.append(createEvent("This is test message numer {}.", i)); + } + appender.stop(DEFAULT_SHUTDOWN_MS, TimeUnit.MILLISECONDS); + + assertThat(extensionFolder).isDirectoryContaining("glob:**/*." + fileExtension); + + final FileExtension ext = FileExtension.lookup(fileExtension); + if (ext == null || FileExtension.ZIP == ext || FileExtension.PACK200 == ext) { + return; // Apache Commons Compress cannot deflate zip? TODO test decompressing these + // formats + } + + for (final Path file : Files.newDirectoryStream(extensionFolder)) { + if (file.getFileName().endsWith(fileExtension)) { + try (final InputStream fis = Files.newInputStream(file); + final InputStream in = new CompressorStreamFactory() + .createCompressorInputStream(toRootLowerCase(ext.name()), fis)) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + assertThat(in).as("compressed input stream").isNotNull(); + assertDoesNotThrow(() -> IOUtils.copy(in, baos)); + final String text = new String(baos.toByteArray(), Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + assertThat(lines) + .allMatch(message -> + MESSAGE_PATTERN.matcher(message).matches()); + } + } + } + } finally { + if (appender.isStarted()) { + appender.stop(DEFAULT_SHUTDOWN_MS, TimeUnit.MILLISECONDS); + } + FileUtils.deleteDirectory(extensionFolder.toFile()); + } + } +} 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..abe97560e82 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.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.rolling; + +import static org.junit.Assert.assertNotNull; +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.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.logging.log4j.Logger; +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; + +/** + * LOG4J2-2602. + */ +public class RollingAppenderSizeWithTimeTest { + + private static final String CONFIG = "log4j-rolling-size-with-time.xml"; + + private static final String DIR = "target/rolling-size-test"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderSizeWithTimeTest.class.getName()); + } + + @Test + public void testAppender() 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); + } + } + if (!loggerContextRule.getLoggerContext().stop(30, TimeUnit.SECONDS)) { + System.err.println("Could not stop cleanly " + loggerContextRule + " for " + this); + } + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists()); + final File[] files = dir.listFiles(); + assertNotNull(files); + for (final File file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (final 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 = new String(baos.toByteArray(), Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertTrue("Log messages lost : " + messages.size(), messages.isEmpty()); + assertTrue("Files not rolled : " + files.length, files.length > 2); + } +} 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..579768097f2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.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.appender.rolling; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; +import java.nio.file.Files; +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 java.util.stream.Collectors; +import java.util.stream.StreamSupport; +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.status.StatusLogger; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * LOG4J2-1766. + */ +@UsingStatusListener +class RollingAppenderTempCompressedFilePatternTest { + + private static Logger LOGGER = StatusLogger.getLogger(); + + @TempLoggingDir + private static Path loggingPath; + + @Test + @DisabledOnOs(value = OS.MAC, disabledReason = "FileWatcher isn't fast enough to work properly.") + @LoggerContextSource + void testAppender(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(getClass()); + final Path logsDir = loggingPath.resolve("logs"); + final Path tmpDir = loggingPath.resolve("tmp"); + Files.createDirectories(tmpDir); + try (final WatchService watcher = FileSystems.getDefault().newWatchService()) { + WatchKey key = tmpDir.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 (!context.stop(30, TimeUnit.SECONDS)) { + LOGGER.error( + "Could not stop logger context {} cleanly in {}.", + context.getName(), + getClass().getSimpleName()); + } + + int gzippedFiles = 0; + final List files = StreamSupport.stream( + Files.newDirectoryStream(logsDir).spliterator(), false) + .collect(Collectors.toList()); + for (final Path file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final FileExtension ext = + FileExtension.lookupForFile(file.getFileName().toString()); + if (ext != null) { + gzippedFiles++; + } + try (final InputStream fis = Files.newInputStream(file); + final InputStream in = ext != null + ? new CompressorStreamFactory() + .createCompressorInputStream(toRootLowerCase(ext.name()), fis) + : fis) { + assertThat(in).as("compressed input stream").isNotNull(); + assertDoesNotThrow(() -> IOUtils.copy(in, baos)); + } + final String text = new String(baos.toByteArray(), Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertThat(messages).as("Lost messages").isEmpty(); + assertThat(files).as("Log files").hasSizeGreaterThan(16); + assertThat(gzippedFiles).as("Compressed log file count").isGreaterThan(16); + + 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++; + } + } + assertThat(temporaryFilesCreated) + .as("Temporary files created") + .isGreaterThan(0) + .isEqualTo(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..31c04d39091 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeDirectTest.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 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.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import org.apache.logging.log4j.Logger; +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; + +/** + * + */ +public class RollingAppenderTimeAndSizeDirectTest { + + private static final String CONFIG = "log4j-rolling3-direct.xml"; + + private static final String DIR = "target/rolling3Direct"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderTimeAndSizeDirectTest.class.getName()); + } + + @Test + public void testAppender() 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("Directory not created", dir.exists() && dir.listFiles().length > 0); + 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..a04471dcac1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.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.appender.rolling; + +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.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.FileTime; +import java.util.Arrays; +import java.util.Random; +import org.apache.logging.log4j.Logger; +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; + +/** + * + */ +public class RollingAppenderTimeAndSizeTest { + + private static final String CONFIG = "log4j-rolling3.xml"; + + private static final String DIR = "target/rolling3/test"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingAppenderTimeAndSizeTest.class.getName()); + } + + @Test + public void testAppender() throws Exception { + final Random rand = new Random(); + final File logFile = new File("target/rolling3/rollingtest.log"); + assertTrue("target/rolling3/rollingtest.log does not exist", logFile.exists()); + final FileTime time = (FileTime) Files.getAttribute(logFile.toPath(), "creationTime"); + for (int j = 0; j < 100; ++j) { + final 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("Directory not created", dir.exists() && dir.listFiles().length > 0); + final File[] files = dir.listFiles(); + Arrays.sort(files); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log")))))); + final int found = 0; + int fileCounter = 0; + String previous = ""; + for (final File file : files) { + final String actual = file.getName(); + final StringBuilder padding = new StringBuilder(); + final String length = Long.toString(file.length()); + for (int i = length.length(); i < 10; ++i) { + padding.append(" "); + } + final String[] fileParts = actual.split("_|\\."); + fileCounter = previous.equals(fileParts[1]) ? ++fileCounter : 1; + previous = fileParts[1]; + assertEquals( + "Incorrect file name. Expected counter value of " + fileCounter + " in " + actual, + Integer.toString(fileCounter), + fileParts[2]); + } + final FileTime endTime = (FileTime) Files.getAttribute(logFile.toPath(), "creationTime"); + assertNotEquals("Creation times are equal", time, endTime); + } +} 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 77% 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..e5e68f593e1 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 @@ -1,32 +1,31 @@ /* * 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.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.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; @@ -40,7 +39,8 @@ public class RollingAppenderTimeTest { private static final String CONFIG = "log4j-rolling2.xml"; private static final String DIR = "target/rolling2"; - private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); diff --git a/log4j-core/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 similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderUncompressedTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderUncompressedTest.java index 730e1b243b9..66568da524f 100644 --- a/log4j-core/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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -20,12 +20,11 @@ import static org.junit.Assert.assertTrue; import java.io.File; - 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.junit.CleanFolders; +import org.apache.logging.log4j.core.test.junit.CleanFolders; import org.apache.logging.log4j.status.StatusLogger; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -41,7 +40,7 @@ public class RollingAppenderUncompressedTest { private static final String DIR = "target/rolling4"; private final Logger logger = LogManager.getLogger(RollingAppenderUncompressedTest.class.getName()); - + @ClassRule public static CleanFolders rule = new CleanFolders(CONFIG); @@ -59,8 +58,8 @@ public static void cleanupClass() { } @Test - public void testAppender() throws Exception { - for (int i=0; i < 100; ++i) { + public void testAppender() { + for (int i = 0; i < 100; ++i) { logger.debug("This is test message number " + i); } final File dir = new File(DIR); @@ -77,5 +76,4 @@ public void testAppender() throws Exception { } assertTrue("No archived files found", 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..d4b6bb5d6f7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSize3490Test.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 static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +/** + * This test attempts to validate that logging rolls after the max files are already written + * will succeed on a restart. + */ +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 + static void clean() { + final File dir = new File(DIR); + if (dir.exists()) { + final File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + file.delete(); + } + } + dir.delete(); + } + } + + @Test + void rolloverTest() throws Exception { + final File parent = new File(DIR); + parent.mkdirs(); + final Path app1 = new File(parent, "app-21.log").toPath(); + Files.write(app1, Arrays.asList(set1), StandardOpenOption.CREATE_NEW); + final List lines = new ArrayList<>(); + for (int count = 0; count < 1024; count += LINE_2.length()) { + lines.add(LINE_2); + } + final File file2 = new File(parent, "app-22.log"); + final Path app2 = file2.toPath(); + Files.write(app2, lines, StandardOpenOption.CREATE_NEW); + final LoggerContext context = + Configurator.initialize("TestConfig", this.getClass().getClassLoader(), CONFIG); + final RollingFileAppender app = context.getConfiguration().getAppender("RollingFile"); + app.getManager().addRolloverListener(this); + final Logger logger = context.getLogger("Test"); + logger.info("Trigger rollover"); + assertTrue(rolloverTriggered, "Rollover was not triggered"); + } + + @Override + public void rolloverTriggered(final String fileName) {} + + @Override + public void rolloverComplete(final 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..f871587f9b3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.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 static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +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; + +/** + * 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. + */ +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"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private final Map rolloverFiles = new HashMap<>(); + + @Test + public void streamClosedError() throws Exception { + ((RollingFileAppender) loggerContextRule.getAppender("RollingFile")) + .getManager() + .addRolloverListener(this); + final Logger logger = loggerContextRule.getLogger(RollingDirectSizeTimeNewDirectoryTest.class); + + for (int i = 0; i < 1000; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + Thread.sleep(1500); + for (int i = 0; i < 1000; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + + assertTrue("A time based rollover did not occur", rolloverFiles.size() > 1); + final int maxFiles = Collections.max(rolloverFiles.values(), Comparator.comparing(AtomicInteger::get)) + .get(); + assertTrue("No size based rollovers occurred", maxFiles > 1); + } + + @Override + public void rolloverTriggered(final String fileName) {} + + @Override + public void rolloverComplete(final String fileName) { + final File file = new File(fileName); + final String logDir = file.getParentFile().getName(); + final AtomicInteger fileCount = rolloverFiles.computeIfAbsent(logDir, k -> new AtomicInteger(0)); + fileCount.incrementAndGet(); + } +} 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..1d7f83bff31 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.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.appender.rolling; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Arrays; +import java.util.Iterator; +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.test.junit.LoggerContextRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class RollingDirectTimeNewDirectoryTest { + + 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"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + /** + * This test logs directly to the target file. Rollover is set to happen once per second. We pause once + * to ensure a rollover takes place. However, it is possible that 3 or 4 rollovers could occur, depending + * on when the test starts and what else is going on on the machine running the test. + * @throws Exception + */ + @Test + public void streamClosedError() throws Exception { + + final Logger logger = loggerContextRule.getLogger(RollingDirectTimeNewDirectoryTest.class.getName()); + + for (int i = 0; i < 1000; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + Thread.sleep(1500); + for (int i = 0; i < 1000; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + + final File logDir = new File(DIR); + final File[] logFolders = logDir.listFiles(); + assertNotNull(logFolders); + Arrays.sort(logFolders); + int totalFiles = 0; + try { + + final int minExpectedLogFolderCount = 2; + assertTrue( + "was expecting at least " + minExpectedLogFolderCount + " folders, " + "found " + logFolders.length, + logFolders.length >= minExpectedLogFolderCount); + + for (File logFolder : logFolders) { + final File[] logFiles = logFolder.listFiles(); + if (logFiles != null) { + assertTrue("Only 1 file per folder expected: got " + logFiles.length, logFiles.length <= 1); + totalFiles += logFiles.length; + } + } + assertTrue("Expected at least 2 files", totalFiles >= 2); + + } catch (AssertionError error) { + System.out.format("log directory (%s) contents:%n", DIR); + final Iterator fileIterator = + FileUtils.iterateFilesAndDirs(logDir, TrueFileFilter.TRUE, TrueFileFilter.TRUE); + int totalFileCount = 0; + while (fileIterator.hasNext()) { + totalFileCount++; + final File file = fileIterator.next(); + System.out.format("-> %s (%d)%n", file, file.length()); + } + System.out.format("total file count: %d%n", totalFileCount); + throw new AssertionError("check failure", error); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..d2585fdf596 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.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 static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.io.IOException; +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.junit.jupiter.api.Test; + +class RollingFileAppenderAccessTest { + + /** + * Not a real test, just make sure we can compile access to the typed manager. + * + * @throws IOException + */ + @Test + void testAccessManagerWithBuilder() throws IOException { + try (final LoggerContext ctx = LoggerContext.getContext(false)) { + final Configuration config = ctx.getConfiguration(); + final File file = File.createTempFile("RollingFileAppenderAccessTest", ".tmp"); + file.deleteOnExit(); + // @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. + assertNotNull(manager.getRolloverStrategy()); + assertNotNull(manager.getTriggeringPolicy()); + } + } + + /** + * Not a real test, just make sure we can compile access to the typed manager. + * + * @throws IOException + */ + @Test + void testAccessManagerWithStrings() throws IOException { + 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.createAppender( + file.getCanonicalPath(), + "FilePattern", + null, + "Name", + null, + null, + null, + OnStartupTriggeringPolicy.createPolicy(1), + null, + null, + null, + null, + null, + null, + config); + final RollingFileManager manager = appender.getManager(); + // Since the RolloverStrategy and TriggeringPolicy are immutable, we could also use generics to type their + // access. + assertNotNull(manager.getRolloverStrategy()); + assertNotNull(manager.getTriggeringPolicy()); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..d739c978d07 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderBuilderTest.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.core.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.junit.jupiter.api.Test; + +class RollingFileAppenderBuilderTest { + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1620 + */ + @Test + void testDefaultImmediateFlush() { + assertTrue(RollingFileAppender.newBuilder().isImmediateFlush()); + } +} 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..6a568a66fe2 --- /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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertTrue; + +import java.io.File; +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.core.test.junit.CleanFolders; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * 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() { + final 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() { + final Logger logger = loggerContext.getLogger(getClass().getName()); + + 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 + + assertTrue(new File(ROLLING_APPENDER_FILES_DIR, "file-1.log").exists()); + assertTrue(new File(ROLLING_APPENDER_FILES_DIR, "file-2.log").exists()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..6fa5e23f4ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.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.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.junit.jupiter.api.Test; + +class RollingFileAppenderLayoutTest { + + @Test + void testDefaultLayout() { + // @formatter:off + assertNotNull(RollingFileAppender.newBuilder() + .setName(RollingFileAppenderLayoutTest.class.getName()) + .setConfiguration(new DefaultConfiguration()) + .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-test/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 new file mode 100644 index 00000000000..6644ece96a6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.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.appender.rolling; + +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Rule; +import org.junit.Test; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1967 + */ +public class RollingFileAppenderReconfigureTest { + + @Rule + public final LoggerContextRule loggerContextRule = new LoggerContextRule("rolling-file-appender-reconfigure.xml"); + + @Test + public void testReconfigure() { + loggerContextRule.reconfigure(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..84a20894d29 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.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.appender.rolling; + +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Rule; +import org.junit.Test; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1967 + */ +public class RollingFileAppenderReconfigureUndefinedSystemPropertyTest { + + @Rule + 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-test/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 new file mode 100644 index 00000000000..2f63c19e34e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +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.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.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Tests LOG4J2-2009 Rolling appender managers broken on pattern/policy reconfiguration + */ +class RollingFileAppenderUpdateDataTest { + + private ConfigurationBuilder buildConfigA() { + return buildConfigurationBuilder("target/rolling-update-date/foo.log.%i"); + } + + // rebuild config with date based rollover + private ConfigurationBuilder buildConfigB() { + return buildConfigurationBuilder("target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i"); + } + + private ConfigurationBuilder buildConfigurationBuilder(final String filePattern) { + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setConfigurationName("LOG4J2-1964 demo"); + builder.setStatusLevel(Level.ERROR); + // @formatter:off + builder.add( + builder.newAppender("consoleLog", "Console").addAttribute("target", ConsoleAppender.Target.SYSTEM_ERR)); + builder.add(builder.newAppender("fooAppender", "RollingFile") + .addAttribute("fileName", "target/rolling-update-date/foo.log") + .addAttribute("filePattern", filePattern) + .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "10MB"))); + builder.add(builder.newRootLogger(Level.INFO) + .add(builder.newAppenderRef("consoleLog")) + .add(builder.newAppenderRef("fooAppender"))); + // @formatter:on + return builder; + } + + private LoggerContext loggerContext1 = null; + private LoggerContext loggerContext2 = null; + + @AfterEach + void after() { + if (loggerContext1 != null) { + loggerContext1.close(); + loggerContext1 = null; + } + if (loggerContext2 != null) { + loggerContext2.close(); + loggerContext2 = null; + } + } + + @Test + void testClosingLoggerContext() { + // initial config with indexed rollover + try (final LoggerContext loggerContext1 = + Configurator.initialize(buildConfigA().build())) { + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i"); + } + + // rebuild config with date based rollover + try (final LoggerContext loggerContext2 = + Configurator.initialize(buildConfigB().build())) { + validateAppender(loggerContext2, "target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i"); + } + } + + @Test + void testNotClosingLoggerContext() { + // 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 + loggerContext2 = Configurator.initialize(buildConfigB().build()); + assertNotNull(loggerContext2, "No LoggerContext"); + assertSame(loggerContext1, loggerContext2, "Expected same logger context to be returned"); + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i"); + } + + @Test + 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"); + assertNotNull(appender); + assertEquals(expectedFilePattern, appender.getFilePattern()); + LogManager.getLogger("root").info("just to show it works."); + } +} 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..78784cfa755 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.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.logging.log4j.core.appender.rolling; + +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.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.IOUtils; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.Issue; + +class RollingFileManagerTest { + + /** + * Test the RollingFileManager with a custom DirectFileRolloverStrategy + * + * @throws IOException + */ + @Test + void testCustomDirectFileRolloverStrategy() throws IOException { + class CustomDirectFileRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy { + final File file; + + CustomDirectFileRolloverStrategy(final File file, final StrSubstitutor strSubstitutor) { + super(strSubstitutor); + this.file = file; + } + + @Override + public String getCurrentFileName(final RollingFileManager manager) { + return file.getAbsolutePath(); + } + + @Override + public void clearCurrentFileName() { + // do nothing + } + + @Override + public RolloverDescription rollover(final 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(); + + assertNotNull(appender); + final String testContent = "Test"; + try (final RollingFileManager manager = appender.getManager()) { + 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)) { + assertEquals(testContent, IOUtils.toString(reader)); + } + } + } + + /** + * Test that a synchronous action failure does not cause a rollover. Addresses Issue #1445. + */ + @Test + void testSynchronousActionFailure() throws IOException { + class FailingSynchronousAction extends AbstractAction { + @Override + public boolean execute() { + return false; + } + } + class FailingSynchronousStrategy implements RolloverStrategy { + @Override + public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException { + return new RolloverDescriptionImpl(manager.getFileName(), false, new FailingSynchronousAction(), null); + } + } + + final Configuration configuration = new NullConfiguration(); + + // Create the manager. + final File file = File.createTempFile("testSynchronousActionFailure", "log"); + final RollingFileManager manager = RollingFileManager.getFileManager( + file.getAbsolutePath(), + "testSynchronousActionFailure.log.%d{yyyy-MM-dd}", + true, + false, + OnStartupTriggeringPolicy.createPolicy(1), + new FailingSynchronousStrategy(), + null, + PatternLayout.createDefaultLayout(), + 0, + true, + false, + null, + null, + null, + configuration); + assertNotNull(manager); + manager.initialize(); + + // Log something to ensure that the existing file size is > 0 + final String testContent = "Test"; + manager.writeToDestination(testContent.getBytes(StandardCharsets.US_ASCII), 0, testContent.length()); + + // Trigger rollover that will fail + manager.rollover(); + + // If the rollover fails, then the log file should be unchanged + assertEquals(file.getAbsolutePath(), manager.getFileName()); + + // The logged content should be unchanged + assertEquals(testContent, new String(Files.readAllBytes(file.toPath()), StandardCharsets.US_ASCII)); + } + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/1645") + void testCreateParentDir() { + final Configuration configuration = new NullConfiguration(); + final RollingFileManager manager = RollingFileManager.getFileManager( + null, + "testCreateParentDir.log.%d{yyyy-MM-dd}", + true, + false, + NoOpTriggeringPolicy.INSTANCE, + DirectWriteRolloverStrategy.newBuilder() + .setConfig(configuration) + .build(), + null, + PatternLayout.createDefaultLayout(configuration), + 0, + true, + true, + null, + null, + null, + configuration); + assertNotNull(manager); + try { + final File file = new File("file_in_current_dir.log"); + assertNull(file.getParentFile()); + manager.createParentDir(file); + } catch (final Throwable t) { + fail("createParentDir failed: " + t.getMessage()); + } finally { + manager.close(); + } + } + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/2592") + void testRolloverOfDeletedFile() throws IOException { + final File file = File.createTempFile("testRolloverOfDeletedFile", "log"); + file.deleteOnExit(); + final String testContent = "Test"; + try (final OutputStream os = + new ByteArrayOutputStream(); // use a dummy OutputStream so that the real file can be deleted + final RollingFileManager manager = new RollingFileManager( + null, + file.getAbsolutePath(), + "testRolloverOfDeletedFile.log.%d{yyyy-MM-dd}", + os, + true, + false, + 0, + System.currentTimeMillis(), + OnStartupTriggeringPolicy.createPolicy(1), + DefaultRolloverStrategy.newBuilder().build(), + file.getName(), + null, + null, + null, + null, + false, + ByteBuffer.allocate(256))) { + assertTrue(file.delete()); + manager.setRenameEmptyFiles(true); + manager.rollover(); + assertEquals(file.getAbsolutePath(), manager.getFileName()); + manager.writeBytes(testContent.getBytes(StandardCharsets.US_ASCII), 0, testContent.length()); + } + assertEquals(testContent, new String(Files.readAllBytes(file.toPath()), StandardCharsets.US_ASCII)); + } +} 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..e720a33ea0f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingNewDirectoryTest.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.appender.rolling; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import org.apache.logging.log4j.Logger; +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; + +/** + * Tests + */ +public class RollingNewDirectoryTest { + private static final String CONFIG = "log4j-rolling-new-directory.xml"; + + private static final String DIR = "target/rolling-new-directory"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingNewDirectoryTest.class.getName()); + } + + @Test + public void streamClosedError() throws Exception { + for (int i = 0; i < 10; ++i) { + logger.info("AAA"); + Thread.sleep(300); + } + final File dir = new File(DIR); + assertNotNull("No directory created", dir); + assertTrue("Child irectories not created", dir.exists() && dir.listFiles().length > 2); + } +} 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..5937242be68 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.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.appender.rolling; + +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.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +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 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 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.jupiter.api.Test; + +/** + * Tests the RollingRandomAccessFileManager class. + */ +class RollingRandomAccessFileManagerTest { + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.rolling.RollingRandomAccessFileManager#writeBytes(byte[], int, int)} + */ + @Test + 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 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; + 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 + 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 + 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 + 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 + 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 + 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 + 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/RollingRandomAppenderDirectWriteAndSwitchDirectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteAndSwitchDirectorTest.java new file mode 100644 index 00000000000..ceb6faacfed --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteAndSwitchDirectorTest.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.appender.rolling; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.time.LocalTime; +import org.apache.logging.log4j.core.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.Test; + +@CleanUpDirectories(RollingRandomAppenderDirectWriteAndSwitchDirectorTest.DIR) +class RollingRandomAppenderDirectWriteAndSwitchDirectorTest { + public static final String DIR = "target/rolling-random-direct-switch-director"; + + @Test + @LoggerContextSource(value = "log4j-rolling-random-direct-switch-director.xml", timeout = 10) + void testAppender(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(RollingRandomAppenderDirectWriteAndSwitchDirectorTest.class.getName()); + final LocalTime start = LocalTime.now(); + LocalTime end; + do { + end = LocalTime.now(); + logger.info("test log"); + Thread.sleep(100); + } while (start.getSecond() == end.getSecond()); + + final File nextLogFile = new File(String.format("%s/%d/%d.log", DIR, end.getSecond(), end.getSecond())); + assertTrue(nextLogFile.exists(), "nextLogFile not created"); + } +} 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..d74a0637bd2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteTest.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.appender.rolling; + +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.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import org.apache.logging.log4j.Logger; +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; + +/** + * + */ +public class RollingRandomAppenderDirectWriteTest { + + private static final String CONFIG = "log4j-rolling-random-direct.xml"; + + private static final String DIR = "target/rolling-random-direct"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingRandomAppenderDirectWriteTest.class.getName()); + } + + @Test + public void testAppender() 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("Directory not created", dir.exists() && dir.listFiles().length > 0); + 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/RollingRandomAppenderDirectWriteWithFilenameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java new file mode 100644 index 00000000000..8ae8e94eb34 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.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.appender.rolling; + +import static org.junit.Assert.assertFalse; + +import java.io.File; +import org.apache.logging.log4j.Logger; +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; + +/** + * + */ +public class RollingRandomAppenderDirectWriteWithFilenameTest { + + private static final String CONFIG = "log4j2-random-1833.xml"; + + private static final String DIR = "target/random-1833"; + + public static LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private Logger logger; + + @Before + public void setUp() { + this.logger = loggerContextRule.getLogger(RollingRandomAppenderDirectWriteWithFilenameTest.class.getName()); + } + + @Test + public void testAppender() { + final File dir = new File(DIR); + assertFalse("Directory created", dir.exists()); + } +} 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..15b6a18ff71 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.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.appender.rolling; + +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 java.util.regex.Matcher; +import org.junit.jupiter.api.Test; + +/** + * Test getEligibleFiles method. + */ +class RolloverFilePatternTest { + + @Test + void testFilePatternWithoutPadding() { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%i.log.gz"); + assertTrue(matcher.find()); + assertNull(matcher.group("ZEROPAD")); + assertNull(matcher.group("PADDING")); + } + + @Test + void testFilePatternWithSpacePadding() { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%3i.log.gz"); + assertTrue(matcher.find()); + assertNull(matcher.group("ZEROPAD")); + assertEquals("3", matcher.group("PADDING")); + } + + @Test + void testFilePatternWithZeroPadding() { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%03i.log.gz"); + assertTrue(matcher.find()); + assertEquals("0", matcher.group("ZEROPAD")); + assertEquals("3", matcher.group("PADDING")); + } + + @Test + void testFilePatternUnmatched() { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%n.log.gz"); + assertFalse(matcher.find()); + } +} 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..d31ecf5ec00 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +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.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +/** + * Tests that zero-padding in rolled files works correctly. + */ +@UsingStatusListener +class RolloverWithPaddingTest { + + private static final String[] EXPECTED_FILES = { + "rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log" + }; + private static final byte[] NOT_EMPTY_CONTENT = "Not empty".getBytes(); + + @TempLoggingDir + private Path loggingPath; + + @Test + @LoggerContextSource + void testPadding(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.fatal("This is a test message number " + i); // 30 chars: + } + + assertThat(loggingPath).isDirectory(); + final List actual = sortedLogFiles(loggingPath); + assertThat(actual).containsExactly(EXPECTED_FILES); + } + + @Test + @LoggerContextSource + void testOldFileDeleted(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(getClass()); + // Prepare directory + for (int i = 1; i <= 5; i++) { + final Path file = loggingPath.resolve("test-00" + i + ".log"); + if (i == 1) { + assertDoesNotThrow(() -> Files.deleteIfExists(file)); + } else { + assertDoesNotThrow(() -> { + try (final OutputStream os = Files.newOutputStream(file)) { + os.write(NOT_EMPTY_CONTENT); + } + }); + } + } + + 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: + } + final List actual = sortedLogFiles(loggingPath); + assertThat(actual).containsExactly(EXPECTED_FILES); + } + + private static List sortedLogFiles(final Path loggingPath) throws IOException { + try (final DirectoryStream stream = Files.newDirectoryStream(loggingPath)) { + return StreamSupport.stream(stream.spliterator(), false) + .map(p -> p.getFileName().toString()) + .sorted() + .collect(Collectors.toList()); + } + } +} 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..1e04d8491a2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.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 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; + +import java.io.IOException; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +@SetSystemProperty(key = "log4j2.StatusLogger.level", value = "WARN") +class AbstractActionTest { + + // Test for LOG4J2-2658 + @Test + void testExceptionsAreLoggedToStatusLogger() { + final StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new TestAction().run(); + final List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + final StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + final String formattedMessage = statusData.getFormattedStatus(); + assertThat( + formattedMessage, + containsString("Exception reported by action 'class org.apache." + + "logging.log4j.core.appender.rolling.action.AbstractActionTest$TestAction'" + + System.lineSeparator() + + "java.io.IOException: failed" + System.lineSeparator() + + "\tat org.apache.logging.log4j.core.appender.rolling.action.AbstractActionTest" + + "$TestAction.execute(AbstractActionTest.java:")); + } + + @Test + void testRuntimeExceptionsAreLoggedToStatusLogger() { + final StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new AbstractAction() { + @Override + public boolean execute() { + throw new IllegalStateException(); + } + }.run(); + final List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + final StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + final String formattedMessage = statusData.getFormattedStatus(); + assertThat(formattedMessage, containsString("Exception reported by action")); + } + + @Test + void testErrorsAreLoggedToStatusLogger() { + final StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new AbstractAction() { + @Override + public boolean execute() { + throw new AssertionError(); + } + }.run(); + final List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + final StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + final 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-test/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 new file mode 100644 index 00000000000..0907ef1e8d5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.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.logging.log4j.core.appender.rolling.action; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests Bzip2CompressAction. + */ +class Bzip2CompressActionTest { + + @Test + void testConstructorDisallowsNullSource() { + assertThrows(NullPointerException.class, () -> new CommonsCompressAction("bzip2", null, new File("any"), true)); + } + + @Test + void testConstructorDisallowsNullDestination() { + assertThrows(NullPointerException.class, () -> new CommonsCompressAction("bzip2", new File("any"), null, true)); + } + + @Test + void testExecuteReturnsFalseIfSourceDoesNotExist() throws IOException { + File source = new File("any"); + while (source.exists()) { + source = new File(source.getName() + Math.random()); + } + final boolean actual = CommonsCompressAction.execute("bzip2", source, new File("any2"), true); + assertFalse(actual, "Cannot compress non-existing file"); + } + + @Test + 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(tempDir, "compressme"); + try (final FileWriter fw = new FileWriter(source, false)) { + fw.write(LINE1); + fw.write(LINE2); + fw.write(LINE3); + fw.flush(); + } + 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); + 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, + (byte) 0x00, + (byte) 0x00, + (byte) 0x1C, + (byte) 0xDF, + (byte) 0x80, + (byte) 0x00, + (byte) 0x12, + (byte) 0x40, + (byte) 0x01, + (byte) 0x38, + (byte) 0x10, + (byte) 0x3F, + (byte) 0xFF, + (byte) 0xFF, + (byte) 0xF0, + (byte) 0x26, + (byte) 0x27, + (byte) 0x9C, + (byte) 0x40, + (byte) 0x20, + (byte) 0x00, + (byte) 0x70, + (byte) 0x63, + (byte) 0x4D, + (byte) 0x06, + (byte) 0x80, + (byte) 0x19, + (byte) 0x34, + (byte) 0x06, + (byte) 0x46, + (byte) 0x9A, + (byte) 0x18, + (byte) 0x9A, + (byte) 0x30, + (byte) 0xCF, + (byte) 0xFD, + (byte) 0x55, + (byte) 0x4D, + (byte) 0x0D, + (byte) 0x06, + (byte) 0x9A, + (byte) 0x0C, + (byte) 0x40, + (byte) 0x1A, + (byte) 0x1A, + (byte) 0x34, + (byte) 0x34, + (byte) 0xCD, + (byte) 0x46, + (byte) 0x05, + (byte) 0x6B, + (byte) 0x19, + (byte) 0x92, + (byte) 0x23, + (byte) 0x5E, + (byte) 0xB5, + (byte) 0x2E, + (byte) 0x79, + (byte) 0x65, + (byte) 0x41, + (byte) 0x81, + (byte) 0x33, + (byte) 0x4B, + (byte) 0x53, + (byte) 0x5B, + (byte) 0x62, + (byte) 0x75, + (byte) 0x0A, + (byte) 0x14, + (byte) 0xB6, + (byte) 0xB7, + (byte) 0x37, + (byte) 0xB8, + (byte) 0x38, + (byte) 0xB9, + (byte) 0x39, + (byte) 0xBA, + (byte) 0x2A, + (byte) 0x4E, + (byte) 0xEA, + (byte) 0xEC, + (byte) 0xEE, + (byte) 0xAD, + (byte) 0xE1, + (byte) 0xE5, + (byte) 0x63, + (byte) 0xD3, + (byte) 0x22, + (byte) 0xE8, + (byte) 0x90, + (byte) 0x52, + (byte) 0xA9, + (byte) 0x7A, + (byte) 0x68, + (byte) 0x90, + (byte) 0x5C, + (byte) 0x82, + (byte) 0x0B, + (byte) 0x51, + (byte) 0xBF, + (byte) 0x24, + (byte) 0x61, + (byte) 0x7F, + (byte) 0x17, + (byte) 0x72, + (byte) 0x45, + (byte) 0x38, + (byte) 0x50, + (byte) 0x90, + (byte) 0x9C, + (byte) 0xE1, + (byte) 0xE8, + (byte) 0x2D + }; + assertEquals(bz2.length, destination.length()); + + // check the compressed contents + try (final FileInputStream fis = new FileInputStream(destination)) { + final byte[] actualBz2 = new byte[bz2.length]; + int n = 0; + int offset = 0; + do { + n = fis.read(actualBz2, offset, actualBz2.length - offset); + offset += n; + } while (offset < actualBz2.length); + assertArrayEquals(bz2, actualBz2, "Compressed data corrupt"); + } + + // uncompress + try (final BZip2CompressorInputStream bzin = new BZip2CompressorInputStream(new ByteArrayInputStream(bz2))) { + final StringBuilder sb = new StringBuilder(); + final byte[] buf = new byte[1024]; + int n = 0; + while ((n = bzin.read(buf, 0, buf.length)) > -1) { + sb.append(new String(buf, 0, n)); + } + assertEquals(LINE1 + LINE2 + LINE3, sb.toString()); + } + } +} 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 79% 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 index 12b61f6421a..e69d9c83d1a 100644 --- 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 @@ -1,27 +1,24 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; - /** * Test helper class. */ diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..06548d5b2c1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.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.appender.rolling.action; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.FileSystems; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitor; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.junit.jupiter.api.Test; + +/** + * Tests the {@code DeleteAction} class. + */ +class DeleteActionTest { + + private static DeleteAction createAnyFilter( + final String path, final boolean followLinks, final int maxDepth, final boolean testMode) { + final PathCondition[] pathFilters = {new FixedCondition(true)}; + return create(path, followLinks, maxDepth, testMode, pathFilters); + } + + 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, null, config); + return delete; + } + + @Test + void testGetBasePathResolvesLookups() { + final DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 1, false); + + final Path actual = delete.getBasePath(); + final String expected = System.getProperty("user.home") + "/a/b/c"; + + assertEquals(FileSystems.getDefault().getPath(expected), actual); + } + + @Test + void testGetBasePathStringReturnsOriginalParam() { + final DeleteAction delete = createAnyFilter("${sys:user.home}/a/b/c", false, 1, false); + assertEquals("${sys:user.home}/a/b/c", delete.getBasePathString()); + } + + @Test + void testGetMaxDepthReturnsConstructorValue() { + final DeleteAction delete = createAnyFilter("any", false, 23, false); + assertEquals(23, delete.getMaxDepth()); + } + + @Test + void testGetOptionsReturnsEmptySetIfNotFollowingLinks() { + final DeleteAction delete = createAnyFilter("any", false, 0, false); + assertEquals(Collections.emptySet(), delete.getOptions()); + } + + @Test + void testGetOptionsReturnsSetWithFollowLinksIfFollowingLinks() { + final DeleteAction delete = createAnyFilter("any", true, 0, false); + assertEquals(EnumSet.of(FileVisitOption.FOLLOW_LINKS), delete.getOptions()); + } + + @Test + void testGetFiltersReturnsConstructorValue() { + final PathCondition[] filters = {new FixedCondition(true), new FixedCondition(false)}; + + final DeleteAction delete = create("any", true, 0, false, filters); + assertEquals(Arrays.asList(filters), delete.getPathConditions()); + } + + @Test + void testCreateFileVisitorReturnsDeletingVisitor() { + final DeleteAction delete = createAnyFilter("any", true, 0, false); + final FileVisitor visitor = delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions()); + assertThat(visitor, instanceOf(DeletingVisitor.class)); + } + + @Test + void testCreateFileVisitorTestModeIsActionTestMode() { + final DeleteAction delete = createAnyFilter("any", true, 0, false); + assertFalse(delete.isTestMode()); + final FileVisitor visitor = delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions()); + 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()); + assertThat(testVisitor, instanceOf(DeletingVisitor.class)); + assertTrue(((DeletingVisitor) testVisitor).isTestMode()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..871f92789ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.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.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +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.jupiter.api.Test; + +/** + * Tests the {@code DeletingVisitor} class. + */ +class DeletingVisitorTest { + /** + * Modifies {@code DeletingVisitor} for testing: instead of actually deleting a file, it adds the path to a list for + * later verification. + */ + static class DeletingVisitorHelper extends DeletingVisitor { + List deleted = new ArrayList<>(); + + public DeletingVisitorHelper( + final Path basePath, final List pathFilters, final boolean testMode) { + super(basePath, pathFilters, testMode); + } + + @Override + protected void delete(final Path file) { + deleted.add(file); // overrides and stores path instead of deleting + } + } + + @Test + 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, Collections.singletonList(ACCEPT_ALL), false); + + final Path any = Paths.get("/a/b/c/any"); + visitor.visitFile(any, null); + assertTrue(visitor.deleted.contains(any)); + } + + @Test + 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, Collections.singletonList(REJECT_ALL), false); + + final Path any = Paths.get("/a/b/c/any"); + visitor.visitFile(any, null); + assertFalse(visitor.deleted.contains(any)); + } + + @Test + void testAllFiltersMustAcceptOrFileIsNotDeleted() throws IOException { + final Path base = Paths.get("/a/b/c"); + final FixedCondition ACCEPT_ALL = new FixedCondition(true); + final FixedCondition REJECT_ALL = new FixedCondition(false); + final List filters = Arrays.asList(ACCEPT_ALL, ACCEPT_ALL, REJECT_ALL); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, filters, false); + + final Path any = Paths.get("/a/b/c/any"); + visitor.visitFile(any, null); + assertFalse(visitor.deleted.contains(any)); + } + + @Test + void testIfAllFiltersAcceptFileIsDeleted() throws IOException { + final Path base = Paths.get("/a/b/c"); + final FixedCondition ACCEPT_ALL = new FixedCondition(true); + final List filters = Arrays.asList(ACCEPT_ALL, ACCEPT_ALL, ACCEPT_ALL); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, filters, false); + + final Path any = Paths.get("/a/b/c/any"); + visitor.visitFile(any, null); + assertTrue(visitor.deleted.contains(any)); + } + + @Test + void testInTestModeFileIsNotDeletedEvenIfAllFiltersAccept() throws IOException { + final Path base = Paths.get("/a/b/c"); + final FixedCondition ACCEPT_ALL = new FixedCondition(true); + final List filters = Arrays.asList(ACCEPT_ALL, ACCEPT_ALL, ACCEPT_ALL); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, filters, true); + + final Path any = Paths.get("/a/b/c/any"); + visitor.visitFile(any, null); + assertFalse(visitor.deleted.contains(any)); + } + + @Test + void testVisitFileRelativizesAgainstBase() throws IOException { + + final PathCondition filter = new PathCondition() { + + @Override + public boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { + final Path expected = Paths.get("relative"); + assertEquals(expected, relativePath); + return true; + } + + @Override + public void beforeFileTreeWalk() {} + }; + final Path base = Paths.get("/a/b/c"); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Collections.singletonList(filter), false); + + final Path child = Paths.get("/a/b/c/relative"); + visitor.visitFile(child, null); + } + + @Test + 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 + void testIOException() { + final DeletingVisitorHelper visitor = + new DeletingVisitorHelper(Paths.get("/a/b/c"), Collections.emptyList(), true); + final IOException exception = new IOException(); + try { + visitor.visitFileFailed(Paths.get("doesNotExist"), exception); + fail(); + } catch (IOException e) { + assertSame(exception, e); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..c66b937d322 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.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.core.appender.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests the Duration class. + */ +class DurationTest { + + @Test + void testParseFailsIfNullText() { + assertThrows(NullPointerException.class, () -> Duration.parse(null)); + } + + @Test + void testParseFailsIfInvalidPattern() { + assertThrows(IllegalArgumentException.class, () -> Duration.parse("abc")); + } + + @Test + void testParseFailsIfSectionsOutOfOrder() { + assertThrows(IllegalArgumentException.class, () -> Duration.parse("P4DT2M1S3H")); + } + + @Test + void testParseFailsIfTButMissingTime() { + assertThrows(IllegalArgumentException.class, () -> Duration.parse("P1dT")); + } + + @Test + void testParseIsCaseInsensitive() { + assertEquals("P4DT3H2M1S", Duration.parse("p4dt3h2m1s").toString()); + } + + @Test + void testParseAllowsOverflows() { + assertEquals(1000 * 70, Duration.parse("PT70S").toMillis()); + assertEquals(1000 * 70 * 60, Duration.parse("PT70M").toMillis()); + assertEquals(1000 * 25 * 60 * 60, Duration.parse("PT25H").toMillis()); + } + + @Test + void testToMillis() { + assertEquals(0, Duration.ZERO.toMillis()); + assertEquals(1000, Duration.parse("PT1S").toMillis()); + assertEquals(1000 * 2 * 60, Duration.parse("PT2M").toMillis()); + assertEquals(1000 * 3 * 60 * 60, Duration.parse("PT3H").toMillis()); + assertEquals(1000 * 4 * 24 * 60 * 60, Duration.parse("P4D").toMillis()); + final long expected = (1000 * 4 * 24 * 60 * 60) + (1000 * 3 * 60 * 60) + (1000 * 2 * 60) + 1000; + assertEquals(expected, Duration.parse("P4DT3H2M1S").toMillis()); + } + + @Test + void testToString() { + assertEquals("PT0S", Duration.ZERO.toString()); + assertEquals("PT1S", Duration.parse("PT1S").toString()); + assertEquals("PT2M1S", Duration.parse("PT2M1S").toString()); + assertEquals("PT3H2M1S", Duration.parse("PT3H2M1S").toString()); + assertEquals("P4DT3H2M1S", Duration.parse("P4DT3H2M1S").toString()); + } + + @Test + void testPrefixPNotRequired() { + assertEquals("PT1S", Duration.parse("T1S").toString()); + assertEquals("PT2M1S", Duration.parse("T2M1S").toString()); + assertEquals("PT3H2M1S", Duration.parse("T3H2M1S").toString()); + assertEquals("P4DT3H2M1S", Duration.parse("4DT3H2M1S").toString()); + } + + @Test + void testInfixTNotRequired() { + assertEquals("PT1S", Duration.parse("P1S").toString()); + assertEquals("PT2M1S", Duration.parse("P2M1S").toString()); + assertEquals("PT3H2M1S", Duration.parse("P3H2M1S").toString()); + assertEquals("P4DT3H2M1S", Duration.parse("P4D3H2M1S").toString()); + } + + @Test + void testPrefixPAndInfixTNotRequired() { + assertEquals("PT1S", Duration.parse("1S").toString()); + assertEquals("PT2M1S", Duration.parse("2M1S").toString()); + assertEquals("PT3H2M1S", Duration.parse("3H2M1S").toString()); + assertEquals("P4DT3H2M1S", Duration.parse("4D3H2M1S").toString()); + } + + @Test + void testCompareTo() { + assertEquals(-1, Duration.parse("PT1S").compareTo(Duration.parse("PT2S"))); + assertEquals(-1, Duration.parse("PT1M").compareTo(Duration.parse("PT2M"))); + assertEquals(-1, Duration.parse("PT1H").compareTo(Duration.parse("PT2H"))); + assertEquals(-1, Duration.parse("P1D").compareTo(Duration.parse("P2D"))); + + assertEquals(0, Duration.parse("PT1S").compareTo(Duration.parse("PT1S"))); + assertEquals(0, Duration.parse("PT1M").compareTo(Duration.parse("PT1M"))); + assertEquals(0, Duration.parse("PT1H").compareTo(Duration.parse("PT1H"))); + assertEquals(0, Duration.parse("P1D").compareTo(Duration.parse("P1D"))); + + assertEquals(1, Duration.parse("PT2S").compareTo(Duration.parse("PT1S"))); + assertEquals(1, Duration.parse("PT2M").compareTo(Duration.parse("PT1M"))); + assertEquals(1, Duration.parse("PT2H").compareTo(Duration.parse("PT1H"))); + assertEquals(1, Duration.parse("P2D").compareTo(Duration.parse("P1D"))); + + assertEquals(0, Duration.parse("PT1M").compareTo(Duration.parse("PT60S"))); + assertEquals(0, Duration.parse("PT1H").compareTo(Duration.parse("PT60M"))); + assertEquals(0, Duration.parse("PT1H").compareTo(Duration.parse("PT3600S"))); + assertEquals(0, Duration.parse("P1D").compareTo(Duration.parse("PT24H"))); + assertEquals(0, Duration.parse("P1D").compareTo(Duration.parse("PT1440M"))); + } + + @Test + void testEquals() { + assertNotEquals(Duration.parse("PT1S"), (Duration.parse("PT2S"))); + assertNotEquals(Duration.parse("PT1M"), (Duration.parse("PT2M"))); + assertNotEquals(Duration.parse("PT1H"), (Duration.parse("PT2H"))); + assertNotEquals(Duration.parse("P1D"), (Duration.parse("P2D"))); + + assertEquals(Duration.parse("PT1S"), (Duration.parse("PT1S"))); + assertEquals(Duration.parse("PT1M"), (Duration.parse("PT1M"))); + assertEquals(Duration.parse("PT1H"), (Duration.parse("PT1H"))); + assertEquals(Duration.parse("P1D"), (Duration.parse("P1D"))); + + assertEquals(Duration.parse("PT1M"), (Duration.parse("PT60S"))); + assertEquals(Duration.parse("PT1H"), (Duration.parse("PT60M"))); + assertEquals(Duration.parse("PT1H"), (Duration.parse("PT3600S"))); + assertEquals(Duration.parse("P1D"), (Duration.parse("PT24H"))); + assertEquals(Duration.parse("P1D"), (Duration.parse("PT1440M"))); + } +} 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..9b179441db5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameActionTest.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.appender.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.PrintStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class FileRenameActionTest { + + static File tempDir = new File("./target"); + + @AfterEach + void cleanup() { + final File file = new File(tempDir, "newFile.log"); + file.delete(); + } + + @Test + 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); + boolean renameResult = action.execute(); + assertTrue(renameResult, "Rename action returned false"); + assertTrue(dest.exists(), "Renamed file does not exist"); + assertFalse(file.exists(), "Old file exists"); + } + + @Test + 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); + boolean renameResult = action.execute(); + assertTrue(renameResult, "Rename action returned false"); + assertFalse(dest.exists(), "Renamed file should not exist"); + assertFalse(file.exists(), "Old file still exists"); + } + + @Test + 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); + boolean renameResult = action.execute(); + assertTrue(renameResult, "Rename action returned false"); + assertTrue(dest.exists(), "Renamed file should exist"); + assertFalse(file.exists(), "Old file still exists"); + } + + @Test + 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); + boolean renameResult = action.execute(); + assertTrue(renameResult, "Rename action returned false"); + 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..729a5d2e858 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.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.appender.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Locale; +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; + +class FileSizeTest { + + @Test + void testParse() { + assertEquals(5 * 1024, FileSize.parse("5k", 0)); + } + + @Test + @ResourceLock(Resources.LOCALE) + void testParseInEurope() { + // Caveat: Breaks the ability for this test to run in parallel with other tests :( + final 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-test/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 new file mode 100644 index 00000000000..b6bf51e6ae3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FixedCondition.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.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Test helper class. + */ +public class FixedCondition implements PathCondition { + + private final boolean accept; + + public FixedCondition(final boolean accept) { + this.accept = accept; + } + + @Override + public boolean accept(final Path baseDir, final Path path, final BasicFileAttributes attrs) { + return accept; + } + + @Override + public void beforeFileTreeWalk() {} +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..84120c63d53 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.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.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests the IfAccumulatedFileCount class. + */ +class IfAccumulatedFileCountTest { + + @Test + void testGetThresholdCount() { + assertEquals(123, IfAccumulatedFileCount.createFileCountCondition(123).getThresholdCount()); + assertEquals(456, IfAccumulatedFileCount.createFileCountCondition(456).getThresholdCount()); + } + + @Test + void testAccept() { + final int[] counts = {3, 5, 9}; + for (final int count : counts) { + final IfAccumulatedFileCount condition = IfAccumulatedFileCount.createFileCountCondition(count); + for (int i = 0; i < count; i++) { + assertFalse(condition.accept(null, null, null)); + // exact match: does not accept + } + // accept when threshold is exceeded + assertTrue(condition.accept(null, null, null)); + assertTrue(condition.accept(null, null, null)); + } + } + + @Test + void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { + final CountingCondition counter = new CountingCondition(true); + final IfAccumulatedFileCount condition = IfAccumulatedFileCount.createFileCountCondition(3, counter); + + for (int i = 1; i < 10; i++) { + if (i <= 3) { + assertFalse(condition.accept(null, null, null), "i=" + i); + assertEquals(0, counter.getAcceptCount()); + } else { + assertTrue(condition.accept(null, null, null)); + assertEquals(i - 3, counter.getAcceptCount()); + } + } + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAccumulatedFileCount filter = + IfAccumulatedFileCount.createFileCountCondition(30, counter, counter, counter); + filter.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..08fa6aa98d8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.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.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.junit.jupiter.api.Test; + +/** + * Tests the IfAccumulatedFileSize class. + */ +class IfAccumulatedFileSizeTest { + + @Test + void testGetThresholdBytes() { + assertEquals(2, create("2B").getThresholdBytes()); + assertEquals(3, create("3 B").getThresholdBytes()); + assertEquals(2 * 1024, create("2KB").getThresholdBytes()); + assertEquals(3 * 1024, create("3 KB").getThresholdBytes()); + assertEquals(2 * 1024 * 1024, create("2MB").getThresholdBytes()); + 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) { + return IfAccumulatedFileSize.createFileSizeCondition(size); + } + + @Test + void testNotAcceptOnExactMatch() { + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; + for (final String size : sizes) { + final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); + final DummyFileAttributes attribs = new DummyFileAttributes(); + attribs.size = condition.getThresholdBytes(); + assertFalse(condition.accept(null, null, attribs)); + } + } + + @Test + void testAcceptIfExceedThreshold() { + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; + for (final String size : sizes) { + final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); + final DummyFileAttributes attribs = new DummyFileAttributes(); + attribs.size = condition.getThresholdBytes() + 1; + assertTrue(condition.accept(null, null, attribs)); + } + } + + @Test + void testNotAcceptIfBelowThreshold() { + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; + for (final String size : sizes) { + final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); + final DummyFileAttributes attribs = new DummyFileAttributes(); + attribs.size = condition.getThresholdBytes() - 1; + assertFalse(condition.accept(null, null, attribs)); + } + } + + @Test + void testAcceptOnceThresholdExceeded() { + final DummyFileAttributes attribs = new DummyFileAttributes(); + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; + for (final String size : sizes) { + final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); + final long quarter = condition.getThresholdBytes() / 4; + attribs.size = quarter; + assertFalse(condition.accept(null, null, attribs)); + assertFalse(condition.accept(null, null, attribs)); + assertFalse(condition.accept(null, null, attribs)); + assertFalse(condition.accept(null, null, attribs)); + assertTrue(condition.accept(null, null, attribs)); + } + } + + @Test + void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { + final CountingCondition counter = new CountingCondition(true); + final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition("2KB", counter); + final DummyFileAttributes attribs = new DummyFileAttributes(); + + final long quarter = condition.getThresholdBytes() / 4; + attribs.size = quarter; + assertFalse(condition.accept(null, null, attribs)); + assertEquals(0, counter.getAcceptCount()); + + assertFalse(condition.accept(null, null, attribs)); + assertEquals(0, counter.getAcceptCount()); + + assertFalse(condition.accept(null, null, attribs)); + assertEquals(0, counter.getAcceptCount()); + + assertFalse(condition.accept(null, null, attribs)); + assertEquals(0, counter.getAcceptCount()); + + assertTrue(condition.accept(null, null, attribs)); + assertEquals(1, counter.getAcceptCount()); + + assertTrue(condition.accept(null, null, attribs)); + assertEquals(2, counter.getAcceptCount()); + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAccumulatedFileSize filter = + IfAccumulatedFileSize.createFileSizeCondition("2GB", counter, counter, counter); + filter.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..8762a4a4912 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.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.appender.rolling.action; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; +import org.apache.logging.log4j.core.config.plugins.util.PluginType; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests the And composite condition. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class IfAllTest { + + @Test + 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 + void testEmptyIsFalse() { + assertFalse(IfAll.createAndCondition().accept(null, null, null)); + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAll and = IfAll.createAndCondition(counter, counter, counter); + and.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } + + @Test + void testCreateAndConditionCalledProgrammaticallyThrowsNPEWhenComponentsNotSpecified() { + PathCondition[] components = null; + assertThrows(NullPointerException.class, () -> IfAll.createAndCondition(components)); + } + + @ParameterizedTest + @ValueSource(strings = "No components provided for IfAll") + void testCreateAndConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenComponentsNotSpecified( + final String expectedMessage) { + final PluginEntry nullEntry = null; + final PluginType type = new PluginType<>(nullEntry, IfAll.class, "Dummy"); + final PluginBuilder builder = new PluginBuilder(type) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(new Node()); + final Object asBuilt = builder.build(); + final List loggerStatusData = StatusLogger.getLogger().getStatusData(); + + assertNull(asBuilt); + assertTrue( + loggerStatusData.stream().anyMatch(e -> e.getFormattedStatus().contains(expectedMessage))); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..9fea2f12986 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.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.appender.rolling.action; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; +import org.apache.logging.log4j.core.config.plugins.util.PluginType; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests the Or composite condition. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class IfAnyTest { + + @Test + 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 + void testEmptyIsFalse() { + assertFalse(IfAny.createOrCondition().accept(null, null, null)); + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAny or = IfAny.createOrCondition(counter, counter, counter); + or.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } + + @Test + void testCreateOrConditionCalledProgrammaticallyThrowsNPEWhenComponentsNotSpecified() { + PathCondition[] components = null; + assertThrows(NullPointerException.class, () -> IfAny.createOrCondition(components)); + } + + @ParameterizedTest + @ValueSource(strings = "No components provided for IfAny") + void testCreateOrConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenComponentsNotSpecified( + final String expectedMessage) { + final PluginEntry nullEntry = null; + final PluginType type = new PluginType<>(nullEntry, IfAny.class, "Dummy"); + final PluginBuilder builder = new PluginBuilder(type) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(new Node()); + final Object asBuilt = builder.build(); + final List loggerStatusData = StatusLogger.getLogger().getStatusData(); + + assertNull(asBuilt); + assertTrue( + loggerStatusData.stream().anyMatch(e -> e.getFormattedStatus().contains(expectedMessage))); + } +} 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 75% 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..8fe68f5c334 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,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.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.jupiter.api.Test; + +class IfFileNameTest { + + @Test + void testCreateNameConditionFailsIfBothRegexAndPathAreNull() { + assertThrows(IllegalArgumentException.class, () -> IfFileName.createNameCondition(null, null)); + } + + @Test + void testCreateNameConditionAcceptsIfEitherRegexOrPathOrBothAreNonNull() { + IfFileName.createNameCondition("bar", null); + IfFileName.createNameCondition(null, "foo"); + IfFileName.createNameCondition("bar", "foo"); + } + + @Test + 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 + 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 + 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 + 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 + 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 + 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 + 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-test/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 new file mode 100644 index 00000000000..41a758fe5c5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.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.appender.rolling.action; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.util.List; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; +import org.apache.logging.log4j.core.config.plugins.util.PluginType; +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests the FileAgeFilter class. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class IfLastModifiedTest { + + @Test + void testAcceptsIfFileAgeEqualToDuration() { + final IfLastModified filter = + IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).build(); + final DummyFileAttributes attrs = new DummyFileAttributes(); + final long age = 33 * 1000; + attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); + assertTrue(filter.accept(null, null, attrs)); + } + + @Test + void testAcceptsIfFileAgeExceedsDuration() { + final IfLastModified filter = + IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).build(); + final DummyFileAttributes attrs = new DummyFileAttributes(); + final long age = 33 * 1000 + 5; + attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); + assertTrue(filter.accept(null, null, attrs)); + } + + @Test + void testDoesNotAcceptIfFileAgeLessThanDuration() { + final IfLastModified filter = + IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).build(); + final DummyFileAttributes attrs = new DummyFileAttributes(); + final long age = 33 * 1000 - 5; + attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); + assertFalse(filter.accept(null, null, attrs)); + } + + @Test + void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { + final CountingCondition counter = new CountingCondition(true); + final IfLastModified filter = IfLastModified.newBuilder() + .setAge(Duration.parse("PT33S")) + .setNestedConditions(counter) + .build(); + final DummyFileAttributes attrs = new DummyFileAttributes(); + final long oldEnough = 33 * 1000 + 5; + attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - oldEnough); + + assertTrue(filter.accept(null, null, attrs)); + assertEquals(1, counter.getAcceptCount()); + assertTrue(filter.accept(null, null, attrs)); + 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)); + assertEquals(3, counter.getAcceptCount()); // no increase + assertFalse(filter.accept(null, null, attrs)); + assertEquals(3, counter.getAcceptCount()); + assertFalse(filter.accept(null, null, attrs)); + assertEquals(3, counter.getAcceptCount()); + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfLastModified filter = IfLastModified.newBuilder() + .setAge(Duration.parse("PT33S")) + .setNestedConditions(counter, counter, counter) + .build(); + filter.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } + + @Test + void testCreateAgeConditionCalledProgrammaticallyThrowsNPEWhenAgeIsNotSpecified() { + Duration age = null; + assertThrows( + NullPointerException.class, () -> IfLastModified.newBuilder().setAge(age)); + } + + @ParameterizedTest + @ValueSource(strings = "No age provided for IfLastModified") + void testCreateAgeConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenAgeIsNotSpecified( + final String expectedMessage) { + final PluginEntry nullEntry = null; + final PluginType type = new PluginType<>(nullEntry, IfLastModified.class, "Dummy"); + final PluginBuilder builder = new PluginBuilder(type) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(new Node()); + final Object asBuilt = builder.build(); + final List loggerStatusData = StatusLogger.getLogger().getStatusData(); + + assertNull(asBuilt); + assertTrue( + loggerStatusData.stream().anyMatch(e -> e.getFormattedStatus().contains(expectedMessage))); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..43637e4d4e2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.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.appender.rolling.action; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; +import org.apache.logging.log4j.core.config.plugins.util.PluginType; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests the Not composite condition. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class IfNotTest { + + @Test + 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 + void testEmptyIsFalse() { + assertThrows( + NullPointerException.class, () -> IfNot.createNotCondition(null).accept(null, null, null)); + } + + @Test + void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfNot not = IfNot.createNotCondition(counter); + not.beforeFileTreeWalk(); + assertEquals(1, counter.getBeforeFileTreeWalkCount()); + } + + @Test + void testCreateNotConditionCalledProgrammaticallyThrowsNPEWhenToNegateIsNotSpecified() { + PathCondition toNegate = null; + assertThrows(NullPointerException.class, () -> IfNot.createNotCondition(toNegate)); + } + + @ParameterizedTest + @ValueSource(strings = "No condition provided for IfNot") + void testCreateNotConditionCalledByPluginBuilderReturnsNullAndLogsMessageWhenToNegateIsNotSpecified( + final String expectedMessage) { + final PluginEntry nullEntry = null; + final PluginType type = new PluginType<>(nullEntry, IfNot.class, "Dummy"); + final PluginBuilder builder = new PluginBuilder(type) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(new Node()); + final Object asBuilt = builder.build(); + final List loggerStatusData = StatusLogger.getLogger().getStatusData(); + + assertNull(asBuilt); + assertTrue( + loggerStatusData.stream().anyMatch(e -> e.getFormattedStatus().contains(expectedMessage))); + } +} 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..5b735c2bd2b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathConditionTest.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.appender.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; + +class PathConditionTest { + + private static final PathCondition[] EMPTY_FIXTURE = {}; + private static final PathCondition[] NULL_FIXTURE = null; + + @Test + 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)); + // + final 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..5e3abad830f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTimeTest.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.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +/** + * Tests the {@code PathSortByModificationTime} class. + */ +class PathSortByModificationTimeTest { + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.rolling.action.PathSortByModificationTime#isRecentFirst()}. + */ + @Test + void testIsRecentFirstReturnsConstructorValue() { + assertTrue(((PathSortByModificationTime) PathSortByModificationTime.createSorter(true)).isRecentFirst()); + assertFalse(((PathSortByModificationTime) PathSortByModificationTime.createSorter(false)).isRecentFirst()); + } + + @Test + 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 + 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/ScriptConditionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptConditionTest.java new file mode 100644 index 00000000000..2361202f7ff --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptConditionTest.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.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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 java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.script.Script; +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests the ScriptCondition class. + */ +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "js, javascript, groovy") +class ScriptConditionTest { + + @Test + void testConstructorDisallowsNullScript() { + assertNull(ScriptCondition.createCondition(null, new DefaultConfiguration())); + } + + @Test + void testConstructorDisallowsNullConfig() { + assertThrows( + NullPointerException.class, + () -> ScriptCondition.createCondition(new Script("test", "js", "print('hi')"), null)); + } + + @Test + void testCreateConditionReturnsNullForNullScript() { + assertNull(ScriptCondition.createCondition(null, new DefaultConfiguration())); + } + + @Test + void testCreateConditionDisallowsNullConfig() { + assertThrows( + NullPointerException.class, + () -> ScriptCondition.createCondition(new Script("test", "js", "print('hi')"), null)); + } + + @Test + void testSelectFilesToDelete() { + final Configuration config = new DefaultConfiguration(); + config.initialize(); // creates the ScriptManager + + final Script script = new Script("test", "javascript", "pathList;"); // script that returns pathList + final ScriptCondition condition = ScriptCondition.createCondition(script, config); + final List pathList = new ArrayList<>(); + final Path base = Paths.get("baseDirectory"); + final List result = condition.selectFilesToDelete(base, pathList); + assertSame(result, pathList); + } + + @Test + void testSelectFilesToDelete2() { + final Configuration config = new DefaultConfiguration(); + config.initialize(); // creates the ScriptManager + + final List pathList = new ArrayList<>(); + pathList.add(new PathWithAttributes(Paths.get("/path/1"), new DummyFileAttributes())); + pathList.add(new PathWithAttributes(Paths.get("/path/2"), new DummyFileAttributes())); + pathList.add(new PathWithAttributes(Paths.get("/path/3"), new DummyFileAttributes())); + + final String scriptText = "pathList.remove(1);" // + + "pathList;"; + final Script script = new Script("test", "javascript", scriptText); + final ScriptCondition condition = ScriptCondition.createCondition(script, config); + final Path base = Paths.get("baseDirectory"); + final List result = condition.selectFilesToDelete(base, pathList); + assertSame(result, pathList); + assertEquals(2, result.size()); + assertEquals(Paths.get("/path/1"), result.get(0).getPath()); + assertEquals(Paths.get("/path/3"), result.get(1).getPath()); + } + + @Test + @Tag("groovy") + void testSelectFilesToDelete3() { + final Configuration config = new DefaultConfiguration(); + config.initialize(); // creates the ScriptManager + + final List pathList = new ArrayList<>(); + pathList.add(new PathWithAttributes(Paths.get("/path/1/abc/a.txt"), new DummyFileAttributes())); + pathList.add(new PathWithAttributes(Paths.get("/path/2/abc/bbb.txt"), new DummyFileAttributes())); + pathList.add(new PathWithAttributes(Paths.get("/path/3/abc/c.txt"), new DummyFileAttributes())); + + final String scriptText = "" // + + "import java.nio.file.*;" // + + "def pattern = ~/(\\d*)[\\/\\\\]abc[\\/\\\\].*\\.txt/;" // + + "assert pattern.getClass() == java.util.regex.Pattern;" // + + "def copy = pathList.collect{it};" + + "pathList.each { pathWithAttribs -> \n" // + + " def relative = basePath.relativize pathWithAttribs.path;" // + + " println 'relative path: ' + relative;" // + + " def str = relative.toString();" + + " def m = pattern.matcher(str);" // + + " if (m.find()) {" // + + " def index = m.group(1) as int;" // + + " println 'extracted index: ' + index;" // + + " def isOdd = (index % 2) == 1;" + + " println 'is odd: ' + isOdd;" // + + " if (isOdd) { copy.remove pathWithAttribs}" + + " }" // + + "}" // + + "println copy;" + + "copy;"; + final Script script = new Script("test", "groovy", scriptText); + final ScriptCondition condition = ScriptCondition.createCondition(script, config); + final Path base = Paths.get("/path"); + final List result = condition.selectFilesToDelete(base, pathList); + assertEquals(1, result.size()); + assertEquals(Paths.get("/path/2/abc/bbb.txt"), result.get(0).getPath()); + } +} 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..a6e854525ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.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.appender.rolling.action; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests the SortingVisitor class. + */ +class SortingVisitorTest { + + @TempDir + Path base; + + private Path aaa; + private Path bbb; + private Path ccc; + + @BeforeEach + 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 + 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 + 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 + void testNoSuchFileFailure() throws IOException { + final SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false)); + assertSame( + FileVisitResult.CONTINUE, + visitor.visitFileFailed(Paths.get("doesNotExist"), new NoSuchFileException("doesNotExist"))); + } + + @Test + void testIOException() { + final SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false)); + final 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/DefaultRouteScriptAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java new file mode 100644 index 00000000000..61c37e49145 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.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.appender.routing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.config.AppenderControl; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * + */ +@RunWith(Parameterized.class) +public class DefaultRouteScriptAppenderTest { + + @Parameterized.Parameters(name = "{0} {1}") + public static Object[][] getParameters() { + // @formatter:off + return new Object[][] { + {"log4j-routing-default-route-script-groovy.xml", false}, + {"log4j-routing-default-route-script-javascript.xml", false}, + {"log4j-routing-script-staticvars-javascript.xml", true}, + {"log4j-routing-script-staticvars-groovy.xml", true}, + }; + // @formatter:on + } + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.SCRIPT_LANGUAGES, "Groovy, Javascript"); + } + + @Rule + public final LoggerContextRule loggerContextRule; + + private final boolean expectBindingEntries; + + public DefaultRouteScriptAppenderTest(final String configLocation, final boolean expectBindingEntries) { + this.loggerContextRule = new LoggerContextRule(configLocation); + this.expectBindingEntries = expectBindingEntries; + } + + private void checkStaticVars() { + final RoutingAppender routingAppender = getRoutingAppender(); + final ConcurrentMap map = routingAppender.getScriptStaticVariables(); + if (expectBindingEntries) { + assertEquals("TestValue2", map.get("TestKey")); + assertEquals("HEXDUMP", map.get("MarkerName")); + } + } + + private ListAppender getListAppender() { + final String key = "Service2"; + final RoutingAppender routingAppender = getRoutingAppender(); + Assert.assertTrue(routingAppender.isStarted()); + final Map appenders = routingAppender.getAppenders(); + final AppenderControl appenderControl = appenders.get(key); + assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl); + final ListAppender listAppender = (ListAppender) appenderControl.getAppender(); + return listAppender; + } + + private RoutingAppender getRoutingAppender() { + return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class); + } + + private void logAndCheck() { + final Marker marker = MarkerManager.getMarker("HEXDUMP"); + final Logger logger = loggerContextRule.getLogger(DefaultRouteScriptAppenderTest.class); + logger.error("Hello"); + final ListAppender listAppender = getListAppender(); + assertEquals("Incorrect number of events", 1, listAppender.getEvents().size()); + logger.error("World"); + assertEquals("Incorrect number of events", 2, listAppender.getEvents().size()); + logger.error(marker, "DEADBEEF"); + assertEquals("Incorrect number of events", 3, listAppender.getEvents().size()); + } + + @Test(expected = AssertionError.class) + public void testAppenderAbsence() { + loggerContextRule.getListAppender("List1"); + } + + @Test + public void testListAppenderPresence() { + // No appender until an event is routed, even thought we initialized the default route on startup. + Assert.assertNull( + "No appender control generated", + getRoutingAppender().getAppenders().get("Service2")); + } + + @Test + public void testNoPurgePolicy() { + // No PurgePolicy in this test + Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy()); + } + + @Test + public void testNoRewritePolicy() { + // No RewritePolicy in this test + Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy()); + } + + @Test + public void testRoutingAppenderDefaultRouteKey() { + final RoutingAppender routingAppender = getRoutingAppender(); + Assert.assertNotNull(routingAppender.getDefaultRouteScript()); + Assert.assertNotNull(routingAppender.getDefaultRoute()); + assertEquals("Service2", routingAppender.getDefaultRoute().getKey()); + } + + @Test + public void testRoutingAppenderPresence() { + getRoutingAppender(); + } + + @Test + public void testRoutingPresence1() { + logAndCheck(); + checkStaticVars(); + } + + @Test + public void testRoutingPresence2() { + logAndCheck(); + checkStaticVars(); + } +} diff --git a/log4j-core/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 similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppender2Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppender2Test.java index d749fdd2036..f57db401175 100644 --- a/log4j-core/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 @@ -1,30 +1,30 @@ /* * 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.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.junit.LoggerContextRule; import org.apache.logging.log4j.message.StructuredDataMessage; import org.junit.Rule; import org.junit.Test; @@ -48,7 +48,7 @@ public void routingTest() { EventLogger.logEvent(msg); final List list = loggerContextRule.getListAppender("List").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("Test", "This is a test", "Unknown"); EventLogger.logEvent(msg); final File file = new File(LOG_FILENAME); diff --git a/log4j-core/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 similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppenderTest.java index 93ed33411d6..886848078c5 100644 --- a/log4j-core/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 @@ -1,30 +1,30 @@ /* * 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.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.junit.LoggerContextRule; import org.apache.logging.log4j.message.StructuredDataMessage; import org.junit.Rule; import org.junit.Test; @@ -48,7 +48,7 @@ public void routingTest() { EventLogger.logEvent(msg); final List list = loggerContextRule.getListAppender("List").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("Test", "This is a test", "Unknown"); EventLogger.logEvent(msg); final File file = new File(LOG_FILENAME); diff --git a/log4j-core/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 similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/PropertiesRoutingAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/PropertiesRoutingAppenderTest.java index 30c3e8d0d9a..76c68ed7849 100644 --- a/log4j-core/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 @@ -1,32 +1,32 @@ /* * 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.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; @@ -50,12 +50,12 @@ public class PropertiesRoutingAppenderTest { public RuleChain rules = loggerContextRule.withCleanFilesRule(UNKNOWN_LOG_FILE, ALERT_LOG_FILE, ACTIVITY_LOG_FILE); @Before - public void setUp() throws Exception { + public void setUp() { this.app = this.loggerContextRule.getListAppender("List"); } @After - public void tearDown() throws Exception { + public void tearDown() { this.app.clear(); this.loggerContextRule.getLoggerContext().stop(); } @@ -66,7 +66,7 @@ public void routingTest() { 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("Test", "This is a test", "Alert"); EventLogger.logEvent(msg); File file = new File(ALERT_LOG_FILE); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java new file mode 100644 index 00000000000..4b838011aa8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.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.logging.log4j.core.appender.routing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +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.Logger; +import org.apache.logging.log4j.core.config.AppenderControl; +import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.Scripts; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.Assert; +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.Parameterized; + +@RunWith(Parameterized.class) +@Category(Scripts.Groovy.class) // technically only half of these tests require groovy +public class RoutesScriptAppenderTest { + + @Parameterized.Parameters(name = "{0} {1}") + public static Object[][] getParameters() { + // @formatter:off + return new Object[][] { + {"log4j-routing-routes-script-groovy.xml", false}, + {"log4j-routing-routes-script-javascript.xml", false}, + {"log4j-routing-script-staticvars-javascript.xml", true}, + {"log4j-routing-script-staticvars-groovy.xml", true}, + }; + // @formatter:on + } + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.SCRIPT_LANGUAGES, "Groovy, JavaScript"); + } + + @Rule + public final LoggerContextRule loggerContextRule; + + private final boolean expectBindingEntries; + + public RoutesScriptAppenderTest(final String configLocation, final boolean expectBindingEntries) { + this.loggerContextRule = new LoggerContextRule(configLocation); + this.expectBindingEntries = expectBindingEntries; + } + + private void checkStaticVars() { + final RoutingAppender routingAppender = getRoutingAppender(); + final ConcurrentMap map = routingAppender.getScriptStaticVariables(); + if (expectBindingEntries) { + assertEquals("TestValue2", map.get("TestKey")); + assertEquals("HEXDUMP", map.get("MarkerName")); + } + } + + private ListAppender getListAppender() { + final String key = "Service2"; + final RoutingAppender routingAppender = getRoutingAppender(); + Assert.assertTrue(routingAppender.isStarted()); + final Map appenders = routingAppender.getAppenders(); + final AppenderControl appenderControl = appenders.get(key); + assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl); + return (ListAppender) appenderControl.getAppender(); + } + + private RoutingAppender getRoutingAppender() { + return loggerContextRule.getRequiredAppender("Routing", RoutingAppender.class); + } + + private void logAndCheck() { + final Marker marker = MarkerManager.getMarker("HEXDUMP"); + final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class); + logger.error("Hello"); + final ListAppender listAppender = getListAppender(); + assertEquals("Incorrect number of events", 1, listAppender.getEvents().size()); + logger.error("World"); + assertEquals("Incorrect number of events", 2, listAppender.getEvents().size()); + logger.error(marker, "DEADBEEF"); + assertEquals("Incorrect number of events", 3, listAppender.getEvents().size()); + } + + @Test(expected = AssertionError.class) + public void testAppenderAbsence() { + loggerContextRule.getListAppender("List1"); + } + + @Test + public void testListAppenderPresence() { + // No appender until an event is routed, even thought we initialized the default route on startup. + Assert.assertNull( + "No appender control generated", + getRoutingAppender().getAppenders().get("Service2")); + } + + @Test + public void testNoPurgePolicy() { + // No PurgePolicy in this test + Assert.assertNull("Unexpected PurgePolicy", getRoutingAppender().getPurgePolicy()); + } + + @Test + public void testNoRewritePolicy() { + // No RewritePolicy in this test + Assert.assertNull("Unexpected RewritePolicy", getRoutingAppender().getRewritePolicy()); + } + + @Test + public void testRoutingAppenderRoutes() { + final RoutingAppender routingAppender = getRoutingAppender(); + assertEquals(expectBindingEntries, routingAppender.getDefaultRouteScript() != null); + assertEquals(expectBindingEntries, routingAppender.getDefaultRoute() != null); + final Routes routes = routingAppender.getRoutes(); + Assert.assertNotNull(routes); + Assert.assertNotNull(routes.getPatternScript()); + final LogEvent logEvent = + DefaultLogEventFactory.getInstance().createEvent("", null, "", Level.ERROR, null, null, null); + assertEquals("Service2", routes.getPattern(logEvent, new ConcurrentHashMap<>())); + } + + @Test + public void testRoutingAppenderPresence() { + getRoutingAppender(); + } + + @Test + public void testRoutingPresence1() { + logAndCheck(); + checkStaticVars(); + } + + @Test + public void testRoutingPresence2() { + logAndCheck(); + checkStaticVars(); + } +} 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..63666788982 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.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.appender.routing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * + */ +public class RoutingAppender2767Test { + private static final String CONFIG = "log4j-routing-2767.xml"; + private static final String ACTIVITY_LOG_FILE = "target/routing1/routingtest-Service.log"; + + private final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG); + + @Rule + public RuleChain rules = loggerContextRule.withCleanFilesRule(ACTIVITY_LOG_FILE); + + @Before + public void setUp() {} + + @After + public void tearDown() { + this.loggerContextRule.getLoggerContext().stop(); + } + + @Test + public void routingTest() throws Exception { + final StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final File file = new File(ACTIVITY_LOG_FILE); + assertTrue("Activity file was not created", file.exists()); + final List lines = Files.lines(file.toPath()).collect(Collectors.toList()); + assertEquals("Incorrect number of lines", 1, lines.size()); + assertTrue("Incorrect content", lines.get(0).contains("This is a test")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender3350Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender3350Test.java new file mode 100644 index 00000000000..cbacf056b84 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender3350Test.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.routing; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class RoutingAppender3350Test { + private static final String CONFIG = "log4j-routing3350.xml"; + private static final String LOG_FILE = "target/tmp/test.log"; + + private final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG); + + @Rule + public RuleChain rules = loggerContextRule.withCleanFilesRule(LOG_FILE); + + @After + public void tearDown() { + this.loggerContextRule.getLoggerContext().stop(); + } + + @Test + public void routingTest() throws IOException { + final String expected = "expectedValue"; + final StringMapMessage message = new StringMapMessage().with("data", expected); + final Logger logger = loggerContextRule.getLoggerContext().getLogger(getClass()); + logger.error(message); + final File file = new File(LOG_FILE); + try (final InputStream inputStream = new FileInputStream(file); + final InputStreamReader streamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + final BufferedReader reader = new BufferedReader(streamReader)) { + final String actual = reader.readLine(); + assertEquals(expected, actual); + } + } +} 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..e44ffc0eaed --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.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.routing; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class RoutingAppenderKeyLookupEvaluationTest { + private static final String CONFIG = "log4j-routing-lookup.xml"; + + private static final String KEY = "user"; + private ListAppender app; + + @Rule + public final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG); + + @Before + public void setUp() { + ThreadContext.remove(KEY); + this.app = this.loggerContextRule.getListAppender("List"); + } + + @After + public void tearDown() { + this.app.clear(); + this.loggerContextRule.getLoggerContext().stop(); + ThreadContext.remove(KEY); + } + + @Test + public void testRoutingNoUser() { + final Logger logger = loggerContextRule.getLogger(getClass()); + logger.warn("no user"); + final String message = app.getMessages().get(0); + assertEquals("WARN ${ctx:user} no user", message); + } + + @Test + public void testRoutingDoesNotMatchRoute() { + final Logger logger = loggerContextRule.getLogger(getClass()); + ThreadContext.put(KEY, "noRouteExists"); + logger.warn("unmatched user"); + assertTrue(app.getMessages().isEmpty()); + } + + @Test + public void testRoutingContainsLookup() { + final Logger logger = loggerContextRule.getLogger(getClass()); + ThreadContext.put(KEY, "${java:version}"); + logger.warn("naughty user"); + final String message = app.getMessages().get(0); + assertEquals("WARN ${java:version} naughty user", message); + } + + @Test + public void testRoutingMatchesEscapedLookup() { + final Logger logger = loggerContextRule.getLogger(getClass()); + ThreadContext.put(KEY, "${upper:name}"); + logger.warn("naughty user"); + final String message = app.getMessages().get(0); + assertEquals("WARN ${upper:name} naughty user", message); + } + + @Test + public void testRoutesThemselvesNotEvaluated() { + final Logger logger = loggerContextRule.getLogger(getClass()); + ThreadContext.put(KEY, "NAME"); + logger.warn("unmatched user"); + assertTrue(app.getMessages().isEmpty()); + } +} diff --git a/log4j-core/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 similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderTest.java index 03ec829e890..eaaa3255e39 100644 --- a/log4j-core/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 @@ -1,37 +1,38 @@ /* * 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.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.rules.RuleChain; -import static org.junit.Assert.*; - /** * */ @@ -49,12 +50,12 @@ public class RoutingAppenderTest { public RuleChain rules = loggerContextRule.withCleanFilesRule(UNKNOWN_LOG_FILE, ALERT_LOG_FILE, ACTIVITY_LOG_FILE); @Before - public void setUp() throws Exception { + public void setUp() { this.app = this.loggerContextRule.getListAppender("List"); } @After - public void tearDown() throws Exception { + public void tearDown() { this.app.clear(); this.loggerContextRule.getLoggerContext().stop(); } @@ -65,7 +66,7 @@ public void routingTest() { 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("Test", "This is a test", "Alert"); EventLogger.logEvent(msg); File file = new File(ALERT_LOG_FILE); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.java new file mode 100644 index 00000000000..0d7f8d83009 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithJndiTest.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.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.Collections; +import java.util.Map; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * RoutingAppenderWithJndiTest + */ +public class RoutingAppenderWithJndiTest { + + public static final String JNDI_CONTEXT_NAME = "java:comp/env/logging/context-name"; + private ListAppender listAppender1; + private ListAppender listAppender2; + + public static LoggerContextRule loggerContextRule = new LoggerContextRule("log4j-routing-by-jndi.xml"); + + @ClassRule + public static RuleChain rules = + RuleChain.outerRule(new JndiRule(initBindings())).around(loggerContextRule); + + private static Map initBindings() { + System.setProperty("log4j2.enableJndiLookup", "true"); + // System.setProperty("log4j2.enableJndiJms", "true"); + // System.setProperty("log4j2.enableJndiContextSelector", "true"); + return Collections.emptyMap(); + } + + @Before + public void before() { + listAppender1 = RoutingAppenderWithJndiTest.loggerContextRule.getListAppender("List1"); + listAppender2 = RoutingAppenderWithJndiTest.loggerContextRule.getListAppender("List2"); + } + + @After + public void after() { + if (listAppender1 != null) { + listAppender1.clear(); + } + if (listAppender2 != null) { + listAppender2.clear(); + } + } + + @Test + @SuppressWarnings("BanJNDI") + public void routingTest() throws NamingException { + // default route when there's no jndi resource + StructuredDataMessage msg = + new StructuredDataMessage("Test", "This is a message from unknown context", "Context"); + EventLogger.logEvent(msg); + final File defaultLogFile = new File("target/routingbyjndi/routingbyjnditest-unknown.log"); + assertTrue("The default log file was not created", defaultLogFile.exists()); + + // now set jndi resource to Application1 + final Context context = new InitialContext(); + context.bind(JNDI_CONTEXT_NAME, "Application1"); + + msg = new StructuredDataMessage("Test", "This is a message from Application1", "Context"); + EventLogger.logEvent(msg); + assertNotNull("No events generated", listAppender1.getEvents()); + assertEquals( + "Incorrect number of events. Expected 1, got " + + listAppender1.getEvents().size(), + 1, + listAppender1.getEvents().size()); + + // now set jndi resource to Application2 + context.rebind(JNDI_CONTEXT_NAME, "Application2"); + + msg = new StructuredDataMessage("Test", "This is a message from Application2", "Context"); + EventLogger.logEvent(msg); + assertNotNull("No events generated", listAppender2.getEvents()); + assertEquals( + "Incorrect number of events. Expected 1, got " + + listAppender2.getEvents().size(), + 1, + listAppender2.getEvents().size()); + assertEquals( + "Incorrect number of events. Expected 1, got " + + listAppender1.getEvents().size(), + 1, + listAppender1.getEvents().size()); + + msg = new StructuredDataMessage("Test", "This is another message from Application2", "Context"); + EventLogger.logEvent(msg); + assertNotNull("No events generated", listAppender2.getEvents()); + assertEquals( + "Incorrect number of events. Expected 2, got " + + listAppender2.getEvents().size(), + 2, + listAppender2.getEvents().size()); + assertEquals( + "Incorrect number of events. Expected 1, got " + + listAppender1.getEvents().size(), + 1, + listAppender1.getEvents().size()); + + // now set jndi resource to Application3. + // The context name, 'Application3', will be used as log file name by the default route. + context.rebind("java:comp/env/logging/context-name", "Application3"); + msg = new StructuredDataMessage("Test", "This is a message from Application3", "Context"); + EventLogger.logEvent(msg); + final File application3LogFile = new File("target/routingbyjndi/routingbyjnditest-Application3.log"); + assertTrue("The Application3 log file was not created", application3LogFile.exists()); + + // now set jndi resource to Application4 + // The context name, 'Application4', will be used as log file name by the default route. + context.rebind("java:comp/env/logging/context-name", "Application4"); + msg = new StructuredDataMessage("Test", "This is a message from Application4", "Context"); + EventLogger.logEvent(msg); + final File application4LogFile = new File("target/routingbyjndi/routingbyjnditest-Application4.log"); + assertTrue("The Application3 log file was not created", application4LogFile.exists()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..9d40d070e2b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 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.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +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.LoggerContextRule; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * Tests Routing appender purge facilities + */ +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"; + private static final String IDLE_LOG_FILE2 = "target/routing-purge-idle/routingtest-2.log"; + private static final String IDLE_LOG_FILE3 = "target/routing-purge-idle/routingtest-3.log"; + private static final String MANUAL_LOG_FILE1 = "target/routing-purge-manual/routingtest-1.log"; + private static final String MANUAL_LOG_FILE2 = "target/routing-purge-manual/routingtest-2.log"; + private static final String MANUAL_LOG_FILE3 = "target/routing-purge-manual/routingtest-3.log"; + + private ListAppender app; + private RoutingAppender routingAppenderIdle; + private RoutingAppender routingAppenderIdleWithHangingAppender; + private RoutingAppender routingAppenderManual; + + private final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFilesRule( + IDLE_LOG_FILE1, IDLE_LOG_FILE2, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE2, MANUAL_LOG_FILE3); + + @Before + public void setUp() { + this.app = this.loggerContextRule.getListAppender("List"); + this.routingAppenderIdle = + this.loggerContextRule.getRequiredAppender("RoutingPurgeIdle", RoutingAppender.class); + this.routingAppenderIdleWithHangingAppender = this.loggerContextRule.getRequiredAppender( + "RoutingPurgeIdleWithHangingAppender", RoutingAppender.class); + this.routingAppenderManual = + this.loggerContextRule.getRequiredAppender("RoutingPurgeManual", RoutingAppender.class); + } + + @After + public void tearDown() { + this.app.clear(); + this.loggerContextRule.getLoggerContext().stop(); + } + + @Test(timeout = 5000) + public void routingTest() throws InterruptedException { + StructuredDataMessage msg = new StructuredDataMessage("1", "This is a test 1", "Service"); + EventLogger.logEvent(msg); + final List list = app.getEvents(); + assertNotNull("No events generated", list); + 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); + // '2' is a referenced list appender + final String[] files = {IDLE_LOG_FILE1, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE3}; + assertFileExistance(files); + final Set expectedAppenderKeys = new HashSet<>(2); + expectedAppenderKeys.add("1"); + expectedAppenderKeys.add("3"); + assertEquals(expectedAppenderKeys, routingAppenderManual.getAppenders().keySet()); + + 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.", + 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.", + 2, + routingAppenderManual.getAppenders().size()); + + routingAppenderManual.deleteAppender("1"); + routingAppenderManual.deleteAppender("2"); + routingAppenderManual.deleteAppender("3"); + + 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); + + assertEquals( + "Incorrect number of appenders with manual purge.", + 1, + routingAppenderManual.getAppenders().size()); + + routingAppenderManual.deleteAppender("5"); + routingAppenderManual.deleteAppender("5"); + + assertEquals( + "Incorrect number of appenders with manual purge.", + 0, + routingAppenderManual.getAppenders().size()); + } + + private void assertFileExistance(final String... files) { + for (final String file : files) { + assertTrue("File should exist - " + file + " file ", new File(file).exists()); + } + } +} diff --git a/log4j-core/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 similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingDefaultAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingDefaultAppenderTest.java index c56e7f52a3c..91eca1321f0 100644 --- a/log4j-core/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 @@ -1,30 +1,30 @@ /* * 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.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.junit.LoggerContextRule; import org.apache.logging.log4j.message.StructuredDataMessage; import org.junit.Rule; import org.junit.Test; @@ -47,7 +47,7 @@ public void routingTest() { EventLogger.logEvent(msg); final List list = loggerContextRule.getListAppender("List").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("Test", "This is a test", "Alert"); EventLogger.logEvent(msg); final File file = new File(LOG_FILE); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java new file mode 100644 index 00000000000..572b2ca4d51 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import java.util.function.LongSupplier; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.impl.ThreadContextTestAccess; +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.spi.LoggerContext; +import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.test.junit.Log4jStaticResources; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.test.junit.UsingTestProperties; +import org.apache.logging.log4j.util.ProviderUtil; +import org.apache.logging.log4j.util.Unbox; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.parallel.ResourceLock; + +@UsingStatusListener +@UsingTestProperties +@ResourceLock(value = Log4jStaticResources.THREAD_CONTEXT) +public abstract class AbstractAsyncThreadContextTestBase { + + private static final int LINE_COUNT = 130; + + private static TestProperties props; + + @BeforeAll + public static void beforeClass() { + props.setProperty("log4j2.enableThreadlocals", true); + props.setProperty("log4j2.asyncLoggerRingBufferSize", 128); // minimum ringbuffer size + props.setProperty("log4j2.asyncLoggerConfigRingBufferSize", 128); // minimum ringbuffer size + } + + protected enum Mode { + ALL_ASYNC, + MIXED, + BOTH_ALL_ASYNC_AND_MIXED; + + void initSelector() { + final ContextSelector selector; + if (this == ALL_ASYNC || this == BOTH_ALL_ASYNC_AND_MIXED) { + selector = new AsyncLoggerContextSelector(); + } else { + selector = new ClassLoaderContextSelector(); + } + LogManager.setFactory(new Log4jContextFactory(selector)); + } + + void initConfigFile() { + // NOTICE: PLEASE DON'T REFACTOR: keep "file" local variable for confirmation in debugger. + final String file = this == ALL_ASYNC // + ? "AsyncLoggerThreadContextTest.xml" // + : "AsyncLoggerConfigThreadContextTest.xml"; + props.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, file); + } + } + + protected enum ContextImpl { + WEBAPP("WebApp", "org.apache.logging.log4j.spi.DefaultThreadContextMap"), + GARBAGE_FREE( + "GarbageFree", "org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"); + + private final String threadContextMap; + private final String implClass; + + ContextImpl(final String threadContextMap, final String implClass) { + this.threadContextMap = threadContextMap; + this.implClass = implClass; + } + + void init() { + props.setProperty("log4j2.threadContextMap", threadContextMap); + ThreadContextTestAccess.init(); + } + + public String getImplClassSimpleName() { + return StringUtils.substringAfterLast(implClass, '.'); + } + + public String getImplClass() { + return implClass; + } + } + + private void init(final ContextImpl contextImpl, final Mode asyncMode) { + asyncMode.initSelector(); + asyncMode.initConfigFile(); + + // Verify that we are using the requested context map + contextImpl.init(); + final ThreadContextMap threadContextMap = ProviderUtil.getProvider().getThreadContextMapInstance(); + assertThat(threadContextMap.getClass().getName()) + .as("Check `ThreadContextMap` implementation") + .isEqualTo(contextImpl.getImplClass()); + } + + private LongSupplier remainingCapacity(final LoggerContext loggerContext, final LoggerConfig loggerConfig) { + final LongSupplier contextSupplier; + if (loggerContext instanceof AsyncLoggerContext) { + final RingBufferAdmin ringBufferAdmin = ((AsyncLoggerContext) loggerContext).createRingBufferAdmin(); + contextSupplier = ringBufferAdmin::getRemainingCapacity; + } else { + contextSupplier = null; + } + if (loggerConfig instanceof AsyncLoggerConfig) { + final RingBufferAdmin ringBufferAdmin = ((AsyncLoggerConfig) loggerConfig) + .createRingBufferAdmin(((org.apache.logging.log4j.core.LoggerContext) loggerContext).getName()); + return contextSupplier == null + ? ringBufferAdmin::getRemainingCapacity + : () -> Math.min(contextSupplier.getAsLong(), ringBufferAdmin.getRemainingCapacity()); + } + return contextSupplier != null ? contextSupplier : () -> Long.MAX_VALUE; + } + + protected void testAsyncLogWritesToLog(final ContextImpl contextImpl, final Mode asyncMode, final Path loggingPath) + throws Exception { + final Path testLoggingPath = loggingPath.resolve(asyncMode.toString()); + props.setProperty("logging.path", testLoggingPath.toString()); + init(contextImpl, asyncMode); + final Path[] files = new Path[] { + testLoggingPath.resolve("AsyncLoggerTest.log"), + testLoggingPath.resolve("SynchronousContextTest.log"), + testLoggingPath.resolve("AsyncLoggerAndAsyncAppenderTest.log"), + testLoggingPath.resolve("AsyncAppenderContextTest.log"), + }; + + ThreadContext.push("stackvalue"); + ThreadContext.put("KEY", "mapvalue"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final LoggerConfig loggerConfig = ((org.apache.logging.log4j.core.Logger) log).get(); + final LoggerContext loggerContext = LogManager.getContext(false); + final String loggerContextName = loggerContext.getClass().getSimpleName(); + final LongSupplier remainingCapacity = remainingCapacity(loggerContext, loggerConfig); + + for (int i = 0; i < LINE_COUNT; i++) { + // buffer may be full + if (i >= 128) { + waitAtMost(500, TimeUnit.MILLISECONDS) + .pollDelay(10, TimeUnit.MILLISECONDS) + .until(() -> remainingCapacity.getAsLong() > 0); + } + 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(); + CoreLoggerContexts.stopLoggerContext(false, files[0].toFile()); // 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); + } + } + LogManager.shutdown(); + FileUtils.deleteDirectory(testLoggingPath.toFile()); + } + + private static String contextMap() { + final ReadOnlyThreadContextMap impl = ThreadContext.getThreadContextMap(); + return impl == null + ? ContextImpl.WEBAPP.getImplClassSimpleName() + : impl.getClass().getSimpleName(); + } + + private void checkResult(final Path file, final String loggerContextName, final ContextImpl contextImpl) + throws IOException { + final String contextDesc = contextImpl + " " + contextImpl.getImplClassSimpleName() + " " + loggerContextName; + try (final BufferedReader reader = Files.newBufferedReader(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; + } + assertThat(line).as("Log file '%s'", file.getFileName()).isEqualTo(expect); + } + assertThat(reader.readLine()).as("Last line").isNull(); + } + } +} 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..430739b225f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.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.async; + +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.Collections; +import java.util.List; +import java.util.stream.Collectors; +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.core.test.appender.FailOnceAppender; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * 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(final 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. + final Configuration configuration = loggerContext.getConfiguration(); + final AsyncAppender asyncAppender = configuration.getAppender("Async"); + assertNotNull(asyncAppender, "couldn't obtain the FailOnceAppender"); + asyncAppender.stop(); + + // Verify the logged message. + final FailOnceAppender failOnceAppender = configuration.getAppender("FailOnce"); + assertNotNull(failOnceAppender, "couldn't obtain the FailOnceAppender"); + assertTrue(failOnceAppender.isFailed(), "FailOnceAppender hasn't failed yet"); + final List accumulatedMessages = failOnceAppender.drainEvents().stream() + .map(LogEvent::getMessage) + .map(Message::getFormattedMessage) + .collect(Collectors.toList()); + assertEquals(Collections.singletonList(lastLogMessage), accumulatedMessages); + + } finally { + System.setProperty(throwableClassNamePropertyName, Strings.EMPTY); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerArgumentFreedOnErrorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerArgumentFreedOnErrorTest.java new file mode 100644 index 00000000000..9a4c9c7c8f6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerArgumentFreedOnErrorTest.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.async; + +import static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; + +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junitpioneer.jupiter.SetSystemProperty; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerArgumentFreedOnErrorTest { + + /** + * Tests events are cleared even after failure. + * + * @see LOG4J2-2725 + */ + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + @SetSystemProperty(key = "log4j2.enableDirectEncoders", value = "true") + @SetSystemProperty(key = "log4j2.enableThreadLocals", value = "true") + @SetSystemProperty(key = "log4j2.formatMsgAsync", value = "true") + void parameters_throwing_exception_should_be_garbage_collected(final TestInfo testInfo) throws Exception { + awaitGarbageCollection(() -> { + final String loggerContextName = String.format("%s-LC", testInfo.getDisplayName()); + try (final LoggerContext loggerContext = new AsyncLoggerContext(loggerContextName)) { + loggerContext.start(); + final Logger logger = loggerContext.getRootLogger(); + final ThrowingMessage parameter = new ThrowingMessage(); + logger.fatal(parameter); + return parameter; + } + }); + } + + private static class ThrowingMessage implements Message, StringBuilderFormattable { + + private ThrowingMessage() {} + + @Override + public String getFormattedMessage() { + throw new Error("Expected"); + } + + @Override + public Object[] getParameters() { + return new Object[0]; + } + + @Override + public Throwable getThrowable() { + return null; + } + + @Override + public void formatTo(final StringBuilder buffer) { + throw new Error("Expected"); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..fbccd7afcc9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlock.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.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerClassLoadDeadlock { + static { + final Logger log = LogManager.getLogger("com.foo.bar.deadlock"); + final Exception e = new Exception(); + // the key to reproducing the problem is to fill up the ring buffer so that + // log.info call will block on ring buffer as well + for (int i = 0; i < AsyncLoggerClassLoadDeadlockTest.RING_BUFFER_SIZE * 2; ++i) { + log.info("clinit", e); + } + } +} 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..7a5301e757e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlockTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +/** + * Test class loading deadlock condition from the LOG4J2-1457 + */ +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerClassLoadDeadlockTest { + + static final int RING_BUFFER_SIZE = 128; + + @BeforeAll + static void beforeClass() { + System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"); + System.setProperty("AsyncLogger.RingBufferSize", String.valueOf(RING_BUFFER_SIZE)); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConsoleTest.xml"); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + void testClassLoaderDeadlock() { + // 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/AsyncLoggerConfigAutoFlushTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAutoFlushTest.java new file mode 100644 index 00000000000..d79ec54848e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAutoFlushTest.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 static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerConfigAutoFlushTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigAutoFlushTest.xml"); + } + + @Test + 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 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"); + } +} 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..b137fdba80a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigErrorOnFormat.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.async; + +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; + +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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.message.AsynchronouslyFormattable; +import org.apache.logging.log4j.message.Message; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerConfigErrorOnFormat { + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j2.enableThreadlocals", "true"); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigErrorOnFormat.xml"); + // Log4jLogEvent.toString invokes message.toString + System.setProperty("log4j2.logEventFactory", DefaultLogEventFactory.class.getName()); + } + + @AfterAll + static void afterClass() { + System.clearProperty("log4j2.enableThreadlocals"); + System.clearProperty("log4j2.logEventFactory"); + } + + @Test + 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 BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + reader.close(); + file.delete(); + + assertThat(line1, containsString("Second message")); + assertNull(line2, "Expected only one line"); + } + + @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 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..ab493c57dd6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.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.async; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.BufferedReader; +import java.nio.file.Files; +import java.nio.file.Path; +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.LoggerContext; +import org.apache.logging.log4j.core.async.AsyncLoggerConfig.RootLogger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +@UsingStatusListener +class AsyncLoggerConfigTest { + + private static final String FQCN = AsyncLoggerConfigTest.class.getName(); + + @TempLoggingDir + private static Path loggingPath; + + @Test + @LoggerContextSource + void testAdditivity(final LoggerContext context) throws Exception { + final Path file = loggingPath.resolve("AsyncLoggerConfigTest.log"); + assertThat(file).isEmptyFile(); + + final Logger log = context.getLogger("com.foo.Bar"); + final String msg = "Additive logging: 2 for the price of 1!"; + log.info(msg); + CoreLoggerContexts.stopLoggerContext(file.toFile()); // stop async thread + + final String location = "testAdditivity"; + try (final BufferedReader reader = Files.newBufferedReader(file)) { + for (int i = 0; i < 2; i++) { + assertThat(reader.readLine()) + .as("Message") + .contains(msg) + .as("Location") + .contains(location); + } + } + } + + @Test + void testIncludeLocationDefaultsToFalse() { + final Configuration configuration = new NullConfiguration(); + final LoggerConfig rootLoggerConfig = + RootLogger.newAsyncRootBuilder().setConfig(configuration).build(); + assertFalse(rootLoggerConfig.isIncludeLocation(), "Include location should default to false for async loggers"); + + final LoggerConfig loggerConfig = AsyncLoggerConfig.newAsyncBuilder() + .setConfig(configuration) + .setLoggerName("com.foo.Bar") + .build(); + assertFalse(loggerConfig.isIncludeLocation(), "Include location should default to false for async loggers"); + } + + @Test + void testSingleFilterInvocation() { + final Configuration configuration = new NullConfiguration(); + final Filter filter = mock(Filter.class); + final LoggerConfig config = AsyncLoggerConfig.newAsyncBuilder() + .setLoggerName(FQCN) + .setConfig(configuration) + .setLevel(Level.INFO) + .setFilter(filter) + .build(); + final Appender appender = mock(Appender.class); + when(appender.isStarted()).thenReturn(true); + when(appender.getName()).thenReturn("test"); + config.addAppender(appender, null, null); + final AsyncLoggerConfigDisruptor disruptor = + (AsyncLoggerConfigDisruptor) configuration.getAsyncLoggerConfigDelegate(); + disruptor.start(); + try { + config.log(FQCN, FQCN, null, Level.INFO, new SimpleMessage(), null); + verify(appender, timeout(500).times(1)).append(any()); + verify(filter, times(1)).filter(any()); + } finally { + disruptor.stop(); + } + } +} 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..1de174cfd29 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest2.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 static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerConfigTest2 { + + @Test + void testConsecutiveReconfigure() throws Exception { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigTest2.xml"); + 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/AsyncLoggerConfigUseAfterShutdownTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java new file mode 100644 index 00000000000..6fe6cc2c5ec --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.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.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerConfigUseAfterShutdownTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigTest.xml"); + } + + @Test + void testNoErrorIfLogAfterShutdown() { + 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: + // 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..0c060d3cb5e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.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.async; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.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.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerConfigWithAsyncEnabledTest { + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j2.enableThreadlocals", "true"); + System.setProperty("log4j2.contextSelector", AsyncLoggerContextSelector.class.getCanonicalName()); + // Reuse the configuration from AsyncLoggerConfigTest4 + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigTest4.xml"); + } + + @AfterAll + static void afterClass() { + System.clearProperty("log4j2.enableThreadlocals"); + System.clearProperty("log4j2.contextSelector"); + } + + @Test + 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"); + final 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(); + + final 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-test/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 new file mode 100644 index 00000000000..c82c054ddc6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.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.core.async; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerContextSelectorInitialStateTest { + + @Test + void testLoggerContextsListInitiallyEmpty() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + assertTrue(selector.getLoggerContexts().isEmpty()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..9079a1a6736 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerContextSelectorTest { + + private static final String FQCN = AsyncLoggerContextSelectorTest.class.getName(); + + @Test + void testContextReturnsAsyncLoggerContext() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + + assertInstanceOf(AsyncLoggerContext.class, context); + } + + @Test + void testContext2ReturnsAsyncLoggerContext() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false, null); + + assertInstanceOf(AsyncLoggerContext.class, context); + } + + @Test + void testLoggerContextsReturnsAsyncLoggerContext() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + selector.getContext(FQCN, null, false); + + final List list = selector.getLoggerContexts(); + assertEquals(1, list.size()); + assertInstanceOf(AsyncLoggerContext.class, list.get(0)); + } + + @Test + void testContextNameIsAsyncLoggerContextWithClassHashCode() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + 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 + void testDependentOnClassLoader() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + assertTrue(selector.isClassLoaderDependent()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..00bb100d797 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.MessageFactory2; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerContextTest { + + @Test + void verify_newInstance(final TestInfo testInfo) { + final String testName = testInfo.getDisplayName(); + try (final AsyncLoggerContext loggerContext = new AsyncLoggerContext(testName)) { + final String loggerName = testName + "-loggerName"; + final MessageFactory2 messageFactory = mock(MessageFactory2.class); + final Logger logger = loggerContext.newInstance(loggerContext, loggerName, messageFactory); + assertThat(logger).isInstanceOf(AsyncLogger.class); + assertThat(logger.getName()).isEqualTo(loggerName); + assertThat((MessageFactory) logger.getMessageFactory()).isSameAs(messageFactory); + } + } +} 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..a9226ed2c2f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.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.async; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +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 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.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerCustomSelectorLocationTest { + + @BeforeAll + static void beforeClass() { + final File file = new File("target", "AsyncLoggerCustomSelectorLocationTest.log"); + file.delete(); + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, CustomAsyncContextSelector.class.getName()); + System.setProperty( + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerCustomSelectorLocationTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + 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()); + } + + public static final class CustomAsyncContextSelector implements ContextSelector { + private static final LoggerContext CONTEXT = new AsyncLoggerContext("AsyncDefault"); + + @Override + public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + return CONTEXT; + } + + @Override + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final boolean currentContext, final URI configLocation) { + return CONTEXT; + } + + @Override + public List getLoggerContexts() { + return Collections.singletonList(CONTEXT); + } + + @Override + public void removeContext(final 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..2a2ec4592f5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultLocationTest.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 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 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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerDefaultLocationTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerDefaultLocationTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testAsyncLogWritesToLog() { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + final 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()); + final 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..438d0ef7288 --- /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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import com.lmax.disruptor.ExceptionHandler; +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.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * 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(Tags.ASYNC_LOGGERS) +class AsyncLoggerEventTranslationExceptionTest { + + @BeforeAll + static void beforeAll() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty("AsyncLogger.ExceptionHandler", TestExceptionHandler.class.getName()); + } + + @AfterAll + static void afterAll() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + System.clearProperty("AsyncLogger.ExceptionHandler"); + } + + @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..c173a9ac2bc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerLocationTest.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 static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerLocationTest { + + @BeforeAll + static void beforeClass() { + final File file = new File("target", "AsyncLoggerLocationTest.log"); + file.delete(); + + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerLocationTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + 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..ce579bef7aa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerNanoTimeTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.core.util.SystemNanoClock; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerNanoTimeTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "NanoTimeToFileTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testAsyncLogUsesNanoTimeClock() throws Exception { + final File file = new File("target", "NanoTimeToFileTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final AsyncLogger log = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + final long before = System.nanoTime(); + log.info("Use actual System.nanoTime()"); + assertInstanceOf(SystemNanoClock.class, log.getNanoClock(), "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"); + assertInstanceOf(DummyNanoClock.class, log.getNanoClock(), "using SystemNanoClock"); + + 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"); + 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..ee5db78fadb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + 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"); + assertInstanceOf(DummyNanoClock.class, log.getNanoClock()); + + 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/AsyncLoggerTestCachedThreadName.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestCachedThreadName.java new file mode 100644 index 00000000000..aaa6089dd84 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestCachedThreadName.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.async; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerTestCachedThreadName { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + 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..12fa5b07e37 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestUncachedThreadName.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.async; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.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.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerTestUncachedThreadName { + + @BeforeAll + static void beforeClass() { + System.setProperty("AsyncLogger.ThreadNameStrategy", "UNCACHED"); + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + 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..d7fc698a957 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.BufferedReader; +import java.nio.file.Files; +import java.nio.file.Path; +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.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.test.junit.UsingTestProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +@UsingTestProperties +@UsingStatusListener +class AsyncLoggerThreadContextTest { + + private static TestProperties props; + + @TempLoggingDir + private static Path loggingPath; + + @BeforeAll + static void beforeClass() { + props.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + } + + @Test + @LoggerContextSource("AsyncLoggerThreadContextTest.xml") + void testAsyncLogWritesToLog() throws Exception { + final Path file = loggingPath.resolve("AsyncLoggerTest.log"); + + 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.toFile()); // stop async thread + + final BufferedReader reader = Files.newBufferedReader(file); + final String line1 = reader.readLine(); + reader.close(); + Files.delete(file); + assertThat(line1).contains(msg, "mapvalue", "stackvalue"); + } +} 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..c784ac2822a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerThreadNameStrategyTest { + @AfterEach + void after() { + System.clearProperty("AsyncLogger.ThreadNameStrategy"); + } + + @BeforeEach + void before() { + System.clearProperty("AsyncLogger.ThreadNameStrategy"); + } + + @Test + void testDefaultIfNotConfigured() { + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns); + } + + @Test + void testDefaultIfInvalidConfig() { + System.setProperty("AsyncLogger.ThreadNameStrategy", "\\%%InValid "); + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns); + } + + @Test + void testUseCachedThreadNameIfConfigured() { + System.setProperty("AsyncLogger.ThreadNameStrategy", "CACHED"); + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.CACHED, tns); + } + + @Test + void testUseUncachedThreadNameIfConfigured() { + System.setProperty("AsyncLogger.ThreadNameStrategy", "UNCACHED"); + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.UNCACHED, tns); + } + + @Test + void testUncachedThreadNameStrategyReturnsCurrentThreadName() { + 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 + void testCachedThreadNameStrategyReturnsCachedThreadName() { + 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..3a50eb2ada2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTimestampMessageTest.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.core.async; + +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 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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +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.Constants; +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.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Confirms that if you log a {@link TimestampMessage} then there are no unnecessary calls to {@link Clock}. + *

+ * See LOG4J2-744. + *

+ */ +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerTimestampMessageTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTimestampMessageTest.xml"); + System.setProperty(ClockFactory.PROPERTY_NAME, PoisonClock.class.getName()); + } + + @AfterAll + static void afterClass() throws IllegalAccessException { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + ClockFactoryTest.resetClocks(); + } + + @Test + void testAsyncLogWritesToLog() throws Exception { + + final File file = new File("target", "AsyncLoggerTimestampMessageTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + assertFalse(PoisonClock.called); + log.info((Message) new TimeMsg("Async logger msg with embedded timestamp", 123456789000L)); + assertFalse(PoisonClock.called); + 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); + assertEquals("123456789000 Async logger msg with embedded timestamp", line1, "line1 correct"); + } + + public static class PoisonClock implements Clock { + public static boolean called = false; + + @Override + public long currentTimeMillis() { + // throw new RuntimeException("This should not have been called"); + called = true; + 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..3c56ad55290 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerUseAfterShutdownTest.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 org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Test for https://issues.apache.org/jira/browse/LOG4J2-639 + */ +@Tag(Tags.ASYNC_LOGGERS) +class AsyncLoggerUseAfterShutdownTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testNoErrorIfLogAfterShutdown() { + 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..1040b044242 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.concurrent.TimeUnit; +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.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +@SetTestProperty( + key = Constants.LOG4J_CONTEXT_SELECTOR, + value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") +@UsingStatusListener +class AsyncLoggersWithAsyncAppenderTest { + + @Test + @LoggerContextSource + void testLoggingWorks(final LoggerContext ctx) throws Exception { + final Logger logger = ctx.getLogger(AsyncLoggersWithAsyncAppenderTest.class); + logger.error("This {} a test", "is"); + logger.warn("Hello {}!", "world"); + final Appender appender = ctx.getConfiguration().getAppender("List"); + assertThat(appender).isInstanceOf(ListAppender.class); + final ListAppender listAppender = (ListAppender) appender; + final List list = ((ListAppender) appender).getMessages(2, 1, TimeUnit.SECONDS); + assertThat(list).as("Log events").hasSize(2); + String msg = list.get(0); + String expected = getClass().getName() + " This {} a test - [is] - This is a test"; + assertThat(msg).isEqualTo(expected); + msg = list.get(1); + expected = getClass().getName() + " Hello {}! - [world] - Hello world!"; + assertThat(msg).isEqualTo(expected); + } +} 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..420406d965c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(AsyncLoggers.class) +public class AsyncLoggersWithAsyncLoggerConfigTest { + + @ClassRule + public static LoggerContextRule context = + new LoggerContextRule("AsyncLoggersWithAsyncLoggerConfigTest.xml", AsyncLoggerContextSelector.class); + + @Test + public void testLoggingWorks() throws Exception { + final Logger logger = LogManager.getLogger(); + logger.error("This is a test"); + logger.warn("Hello world!"); + Thread.sleep(100); + final List list = context.getListAppender("List").getMessages(); + assertNotNull("No events generated", list); + assertEquals("Incorrect number of events. Expected 2, got " + list.size(), 2, list.size()); + String msg = list.get(0); + String expected = getClass().getName() + " This is a test"; + assertEquals("Expected " + expected + ", Actual " + msg, expected, msg); + msg = list.get(1); + expected = getClass().getName() + " Hello world!"; + assertEquals("Expected " + expected + ", Actual " + msg, expected, 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..8a21114e6fc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.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.logging.log4j.core.async; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests the AsyncQueueFullPolicyFactory class. + */ +@Tag(Tags.ASYNC_LOGGERS) +class AsyncQueueFullPolicyFactoryTest { + + @BeforeEach + @AfterEach + void resetProperties() { + System.clearProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER); + System.clearProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL); + } + + @Test + void testCreateReturnsDefaultRouterByDefault() { + final AsyncQueueFullPolicy router = AsyncQueueFullPolicyFactory.create(); + assertEquals(DefaultAsyncQueueFullPolicy.class, router.getClass()); + } + + @Test + void testCreateReturnsDiscardingRouterIfSpecified() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + assertEquals( + DiscardingAsyncQueueFullPolicy.class, + AsyncQueueFullPolicyFactory.create().getClass()); + + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + DiscardingAsyncQueueFullPolicy.class.getSimpleName()); + assertEquals( + DiscardingAsyncQueueFullPolicy.class, + AsyncQueueFullPolicyFactory.create().getClass()); + + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + DiscardingAsyncQueueFullPolicy.class.getName()); + assertEquals( + DiscardingAsyncQueueFullPolicy.class, + AsyncQueueFullPolicyFactory.create().getClass()); + } + + @Test + void testCreateDiscardingRouterDefaultThresholdLevelInfo() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + assertEquals( + Level.INFO, + ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()).getThresholdLevel()); + } + + @Test + void testCreateDiscardingRouterCaseInsensitive() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + toRootLowerCase(AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER)); + assertEquals( + Level.INFO, + ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()).getThresholdLevel()); + } + + @Test + void testCreateDiscardingRouterThresholdLevelCustomizable() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + + for (final Level level : Level.values()) { + System.setProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL, level.name()); + assertEquals( + level, ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()).getThresholdLevel()); + } + } + + public static class CustomRouterDefaultConstructor implements AsyncQueueFullPolicy { + public CustomRouterDefaultConstructor() {} + + @Override + public EventRoute getRoute(final long backgroundThreadId, final Level level) { + return null; + } + } + + public static class DoesNotImplementInterface {} + + @Test + void testCreateReturnsCustomRouterIfSpecified() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + CustomRouterDefaultConstructor.class.getName()); + assertEquals( + CustomRouterDefaultConstructor.class, + AsyncQueueFullPolicyFactory.create().getClass()); + } + + @Test + void testCreateReturnsDefaultRouterIfSpecifiedCustomRouterFails() { + System.setProperty( + AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER, + DoesNotImplementInterface.class.getName()); + assertEquals( + DefaultAsyncQueueFullPolicy.class, + AsyncQueueFullPolicyFactory.create().getClass()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootLoggerDefaultLocationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootLoggerDefaultLocationTest.java new file mode 100644 index 00000000000..e10c5e36bf5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootLoggerDefaultLocationTest.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 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 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.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncRootLoggerDefaultLocationTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncRootLoggerDefaultLocationTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testAsyncLogWritesToLog() { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + final ListAppender app = context.getConfiguration().getAppender("List"); + assertNotNull(app); + final Logger log = context.getLogger("com.foo.Bar"); + final String msg = "Async root logger msg with no location by default"; + log.info(msg); + context.stop(); + assertEquals(1, app.getEvents().size()); + final 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/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 similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java index 49dec04ac86..7c97428c0a4 100644 --- a/log4j-core/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 @@ -1,31 +1,30 @@ /* * 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.logging.log4j.core.async; import java.io.File; import java.net.URISyntaxException; import java.net.URL; - 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.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.CleanFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.FileUtils; -import org.apache.logging.log4j.junit.CleanFiles; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -51,7 +50,7 @@ public void testLog4j2_807() throws InterruptedException, URISyntaxException { final File configFile = FileUtils.fileFromUri(url.toURI()); final Logger logger = LogManager.getLogger(AsyncRootReloadTest.class); - logger.info("Log4j configured, will be reconfigured in aprox. 5 sec"); + logger.info("Log4j configured, will be reconfigured in approx. 5 sec"); configFile.setLastModified(System.currentTimeMillis()); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextDefaultTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextDefaultTest.java new file mode 100644 index 00000000000..50f0e0a837e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextDefaultTest.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.async; + +import java.nio.file.Path; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +// Note: the different ThreadContextMap implementations cannot be parameterized: +// ThreadContext initialization will result in static final fields being set in various components. +// To use a different ThreadContextMap, the test needs to be run in a new JVM. +@Tag(Tags.ASYNC_LOGGERS) +class AsyncThreadContextDefaultTest extends AbstractAsyncThreadContextTestBase { + + @TempLoggingDir + private static Path loggingPath; + + @ParameterizedTest + @EnumSource + void testAsyncLogWritesToLog(Mode asyncMode) throws Exception { + testAsyncLogWritesToLog(ContextImpl.WEBAPP, asyncMode, loggingPath); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java new file mode 100644 index 00000000000..250cc3944ce --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.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.async; + +import java.nio.file.Path; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +// Note: the different ThreadContextMap implementations cannot be parameterized: +// ThreadContext initialization will result in static final fields being set in various components. +// To use a different ThreadContextMap, the test needs to be run in a new JVM. +@Tag(Tags.ASYNC_LOGGERS) +class AsyncThreadContextGarbageFreeTest extends AbstractAsyncThreadContextTestBase { + + @TempLoggingDir + private static Path loggingPath; + + @ParameterizedTest + @EnumSource + void testAsyncLogWritesToLog(final Mode asyncMode) throws Exception { + testAsyncLogWritesToLog(ContextImpl.GARBAGE_FREE, asyncMode, loggingPath); + } +} 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..ed12038e3d5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigGlobalLoggersTest.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 static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.lmax.disruptor.YieldingWaitStrategy; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncWaitStrategyFactoryConfigGlobalLoggersTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty( + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + } + + @Disabled("This test succeeds when run individually but fails when run by Surefire with all other tests") + @Test + void testConfigWaitStrategyAndFactory() { + final AsyncLogger logger = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + assertThat("context is AsyncLoggerContext", context instanceof AsyncLoggerContext); + + final AsyncWaitStrategyFactory asyncWaitStrategyFactory = + context.getConfiguration().getAsyncWaitStrategyFactory(); + assertEquals( + AsyncWaitStrategyFactoryConfigTest.YieldingWaitStrategyFactory.class, + asyncWaitStrategyFactory.getClass()); + assertThat( + "factory is YieldingWaitStrategyFactory", + asyncWaitStrategyFactory instanceof AsyncWaitStrategyFactoryConfigTest.YieldingWaitStrategyFactory); + + final AsyncLoggerDisruptor delegate = logger.getAsyncLoggerDisruptor(); + + assertEquals(YieldingWaitStrategy.class, delegate.getWaitStrategy().getClass()); + assertThat("waitstrategy is YieldingWaitStrategy", delegate.getWaitStrategy() instanceof 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..6eec836432d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigTest.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.core.async; + +import static org.assertj.core.api.Assertions.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.YieldingWaitStrategy; +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.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncWaitStrategyFactoryConfigTest { + + @Test + @LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.xml") + void testConfigWaitStrategyFactory(final LoggerContext context) { + final AsyncWaitStrategyFactory asyncWaitStrategyFactory = + context.getConfiguration().getAsyncWaitStrategyFactory(); + assertEquals(YieldingWaitStrategyFactory.class, asyncWaitStrategyFactory.getClass()); + assertThat( + "factory is YieldingWaitStrategyFactory", + asyncWaitStrategyFactory instanceof YieldingWaitStrategyFactory); + } + + @Test + @LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.properties") + void testConfigWaitStrategyFactoryFromProperties(final LoggerContext context) { + final AsyncWaitStrategyFactory asyncWaitStrategyFactory = + context.getConfiguration().getAsyncWaitStrategyFactory(); + assertEquals(YieldingWaitStrategyFactory.class, asyncWaitStrategyFactory.getClass()); + assertThat( + "factory is YieldingWaitStrategyFactory", + asyncWaitStrategyFactory instanceof YieldingWaitStrategyFactory); + } + + @Test + @LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.xml") + void testWaitStrategy(final LoggerContext context) { + + final org.apache.logging.log4j.Logger logger = context.getRootLogger(); + + final AsyncLoggerConfig loggerConfig = + (AsyncLoggerConfig) ((org.apache.logging.log4j.core.Logger) logger).get(); + final AsyncLoggerConfigDisruptor delegate = + (AsyncLoggerConfigDisruptor) loggerConfig.getAsyncLoggerConfigDelegate(); + assertEquals(YieldingWaitStrategy.class, delegate.getWaitStrategy().getClass()); + assertThat( + "waitstrategy is YieldingWaitStrategy", + delegate.getWaitStrategy() instanceof com.lmax.disruptor.YieldingWaitStrategy); + } + + @Test + @LoggerContextSource("AsyncWaitStrategyIncorrectFactoryConfigTest.xml") + void testIncorrectConfigWaitStrategyFactory(final LoggerContext context) { + final AsyncWaitStrategyFactory asyncWaitStrategyFactory = + context.getConfiguration().getAsyncWaitStrategyFactory(); + assertNull(asyncWaitStrategyFactory); + } + + /** + * Test that when XML element {@code AsyncWaitFactory} has no 'class' attribute. + * + * @param configuration the configuration + */ + @Test + @LoggerContextSource("log4j2-asyncwaitfactoryconfig-3159-nok.xml") + void testInvalidBuilderConfiguration3159(final Configuration configuration) { + assertNull(configuration.getAsyncWaitStrategyFactory(), "The AsyncWaitStrategyFactory should be null."); + } + + /** + * Test that when programmatically building a {@link AsyncWaitStrategyFactoryConfig} a {@code null} + * factory class-name throws an exception. + */ + @Test + void testInvalidProgrammaticConfiguration3159WithNullFactoryClassName() { + assertThrows(IllegalArgumentException.class, () -> AsyncWaitStrategyFactoryConfig.newBuilder() + .setFactoryClassName(null)); + } + + /** + * Test that when programmatically building a {@link AsyncWaitStrategyFactoryConfig} a blank ({@code ""}) + * factory class-name throws an exception. + */ + @Test + void testInvalidProgrammaticConfiguration3159WithEmptyFactoryClassName() { + assertThrows(IllegalArgumentException.class, () -> AsyncWaitStrategyFactoryConfig.newBuilder() + .setFactoryClassName("")); + } + + @Test + @LoggerContextSource("AsyncWaitStrategyIncorrectFactoryConfigTest.xml") + void testIncorrectWaitStrategyFallsBackToDefault( + @Named("WaitStrategyAppenderList") final ListAppender list1, final LoggerContext context) { + final org.apache.logging.log4j.Logger logger = context.getRootLogger(); + + final AsyncLoggerConfig loggerConfig = + (AsyncLoggerConfig) ((org.apache.logging.log4j.core.Logger) logger).get(); + final AsyncLoggerConfigDisruptor delegate = + (AsyncLoggerConfigDisruptor) loggerConfig.getAsyncLoggerConfigDelegate(); + + if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 3) { + assertEquals( + org.apache.logging.log4j.core.async.TimeoutBlockingWaitStrategy.class, + delegate.getWaitStrategy().getClass()); + assertThat( + "waitstrategy is TimeoutBlockingWaitStrategy", + delegate.getWaitStrategy() + instanceof org.apache.logging.log4j.core.async.TimeoutBlockingWaitStrategy); + } else if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 4) { + assertEquals( + com.lmax.disruptor.TimeoutBlockingWaitStrategy.class, + delegate.getWaitStrategy().getClass()); + assertThat( + "waitstrategy is TimeoutBlockingWaitStrategy", + delegate.getWaitStrategy() instanceof com.lmax.disruptor.TimeoutBlockingWaitStrategy); + } else { + fail("Unhandled Disruptor version " + DisruptorUtil.DISRUPTOR_MAJOR_VERSION); + } + } + + 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..1d1abdcd4b3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest.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.async; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty( + ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + } + + @Test + void testIncorrectConfigWaitStrategyFactory() throws Exception { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + assertThat("context is AsyncLoggerContext", context instanceof AsyncLoggerContext); + + final AsyncWaitStrategyFactory asyncWaitStrategyFactory = + context.getConfiguration().getAsyncWaitStrategyFactory(); + assertNull(asyncWaitStrategyFactory); + + final AsyncLogger logger = (AsyncLogger) context.getRootLogger(); + final AsyncLoggerDisruptor delegate = logger.getAsyncLoggerDisruptor(); + if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 3) { + assertEquals( + TimeoutBlockingWaitStrategy.class, + delegate.getWaitStrategy().getClass()); + } else { + assertEquals( + Class.forName("com.lmax.disruptor.TimeoutBlockingWaitStrategy"), + delegate.getWaitStrategy().getClass()); + } + } +} 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..e645e4637f3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.util.List; +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.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Tags.ASYNC_LOGGERS) +class BasicAsyncLoggerContextSelectorTest { + + private static final String FQCN = BasicAsyncLoggerContextSelectorTest.class.getName(); + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BasicAsyncLoggerContextSelector.class.getName()); + } + + @AfterAll + static void afterClass() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + } + + @Test + void testContextReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + + assertInstanceOf(AsyncLoggerContext.class, context); + } + + @Test + void testContext2ReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false, null); + + assertInstanceOf(AsyncLoggerContext.class, context); + } + + @Test + void testLoggerContextsReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + + List list = selector.getLoggerContexts(); + assertEquals(1, list.size()); + assertInstanceOf(AsyncLoggerContext.class, list.get(0)); + + selector.getContext(FQCN, null, false); + + list = selector.getLoggerContexts(); + assertEquals(1, list.size()); + assertInstanceOf(AsyncLoggerContext.class, list.get(0)); + } + + @Test + void testContextNameIsAsyncDefault() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + assertEquals("AsyncDefault", context.getName()); + } + + @Test + void testDependentOnClassLoader() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + assertFalse(selector.isClassLoaderDependent()); + } + + @Test + void testFactoryIsNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } + + @Test + void testLogManagerShutdown() { + final 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..1185b5a7589 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.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.async; + +import java.io.Serializable; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +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.appender.AbstractAppender; +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; + +/** + * Appender that can be halted and resumed, for testing queue-full scenarios. + */ +@Plugin(name = "Blocking", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +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 + final List events = logEvents; + if (events != null) { + events.add(event.toImmutable()); + } + + if (countDownLatch == null) { + return; + } + // block until the test class tells us to continue + try { + countDownLatch.await(); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } + } + + @PluginFactory + public static BlockingAppender createAppender( + @PluginAttribute("name") @Required(message = "No name provided for HangingAppender") final String name, + @PluginElement("Layout") final Layout layout, + @PluginElement("Filter") 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-test/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 new file mode 100644 index 00000000000..27548391ab6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests the DefaultAsyncQueueFullPolicy class. + */ +@Tag(Tags.ASYNC_LOGGERS) +class DefaultAsyncQueueFullPolicyTest { + + private static long currentThreadId() { + return Thread.currentThread().getId(); + } + + private static long otherThreadId() { + return -1; + } + + @Test + void testGetRouteEnqueuesIfQueueFullAndCalledFromDifferentThread() { + final DefaultAsyncQueueFullPolicy router = new DefaultAsyncQueueFullPolicy(); + assertEquals(EventRoute.ENQUEUE, router.getRoute(otherThreadId(), Level.ALL)); + assertEquals(EventRoute.ENQUEUE, router.getRoute(otherThreadId(), Level.OFF)); + } + + @Test + void testGetRouteSynchronousIfQueueFullAndCalledFromSameThread() { + final DefaultAsyncQueueFullPolicy router = new DefaultAsyncQueueFullPolicy(); + assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.ALL)); + assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.OFF)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..c816cbef4a4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.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.async; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests the DiscardingAsyncQueueFullPolicy class.. + */ +@Tag(Tags.ASYNC_LOGGERS) +class DiscardingAsyncQueueFullPolicyTest { + + private static long currentThreadId() { + return Thread.currentThread().getId(); + } + + private static long otherThreadId() { + return -1; + } + + @Test + void testConstructorDisallowsNullThresholdLevel() { + assertThrows(NullPointerException.class, () -> { + new DiscardingAsyncQueueFullPolicy(null); + }); + } + + @Test + void testThresholdLevelIsConstructorValue() { + assertSame(Level.ALL, new DiscardingAsyncQueueFullPolicy(Level.ALL).getThresholdLevel()); + assertSame(Level.OFF, new DiscardingAsyncQueueFullPolicy(Level.OFF).getThresholdLevel()); + assertSame(Level.INFO, new DiscardingAsyncQueueFullPolicy(Level.INFO).getThresholdLevel()); + } + + @Test + void testGetRouteDiscardsIfThresholdCapacityReachedAndLevelEqualOrLessSpecificThanThreshold() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.WARN); + + for (final Level level : new Level[] {Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE, Level.ALL}) { + assertEquals(EventRoute.DISCARD, router.getRoute(currentThreadId(), level), level.name()); + assertEquals(EventRoute.DISCARD, router.getRoute(otherThreadId(), level), level.name()); + assertEquals(EventRoute.DISCARD, router.getRoute(currentThreadId(), level), level.name()); + assertEquals(EventRoute.DISCARD, router.getRoute(otherThreadId(), level), level.name()); + } + } + + @Test + void testGetRouteDiscardsIfQueueFullAndLevelEqualOrLessSpecificThanThreshold() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.WARN); + + for (final Level level : new Level[] {Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE, Level.ALL}) { + assertEquals(EventRoute.DISCARD, router.getRoute(currentThreadId(), level), level.name()); + assertEquals(EventRoute.DISCARD, router.getRoute(otherThreadId(), level), level.name()); + } + } + + @Test + void testGetRouteEnqueuesIfThresholdCapacityReachedButLevelMoreSpecificThanThreshold() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.WARN); + + for (final Level level : new Level[] {Level.ERROR, Level.FATAL, Level.OFF}) { + assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), level), level.name()); + assertEquals(EventRoute.ENQUEUE, router.getRoute(otherThreadId(), level), level.name()); + assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), level), level.name()); + assertEquals(EventRoute.ENQUEUE, router.getRoute(otherThreadId(), level), level.name()); + } + } + + @Test + void testGetRouteEnqueueIfOtherThreadQueueFullAndLevelMoreSpecificThanThreshold() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.WARN); + + for (final Level level : new Level[] {Level.ERROR, Level.FATAL, Level.OFF}) { + assertEquals(EventRoute.ENQUEUE, router.getRoute(otherThreadId(), level), level.name()); + } + } + + @Test + void testGetRouteSynchronousIfCurrentThreadQueueFullAndLevelMoreSpecificThanThreshold() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.WARN); + + for (final Level level : new Level[] {Level.ERROR, Level.FATAL, Level.OFF}) { + assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), level), level.name()); + } + } + + @Test + void testGetDiscardCount() { + final DiscardingAsyncQueueFullPolicy router = new DiscardingAsyncQueueFullPolicy(Level.INFO); + assertEquals(0, DiscardingAsyncQueueFullPolicy.getDiscardCount(router), "initially"); + + assertEquals(EventRoute.DISCARD, router.getRoute(-1L, Level.INFO)); + assertEquals(1, DiscardingAsyncQueueFullPolicy.getDiscardCount(router), "increase"); + + assertEquals(EventRoute.DISCARD, router.getRoute(-1L, Level.INFO)); + assertEquals(2, DiscardingAsyncQueueFullPolicy.getDiscardCount(router), "increase"); + + assertEquals(EventRoute.DISCARD, router.getRoute(-1L, Level.INFO)); + assertEquals(3, DiscardingAsyncQueueFullPolicy.getDiscardCount(router), "increase"); + } +} diff --git a/log4j-core/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 similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java index f63e816366e..1d72e8fc8f3 100644 --- a/log4j-core/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 @@ -1,33 +1,32 @@ /* * 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.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.LogManager; -import org.apache.logging.log4j.categories.AsyncLoggers; import org.apache.logging.log4j.core.config.ConfigurationFactory; +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.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.test.appender.ListAppender; import org.apache.logging.log4j.util.Strings; import org.junit.AfterClass; import org.junit.Assert; @@ -48,10 +47,8 @@ public class Log4j2Jira1688AsyncTest { @BeforeClass public static void beforeClass() { - System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, - AsyncLoggerContextSelector.class.getName()); - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j-list.xml"); + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "log4j-list.xml"); } @AfterClass @@ -61,10 +58,11 @@ public static void afterClass() { @Rule public LoggerContextRule context = new LoggerContextRule("log4j-list.xml"); + private ListAppender listAppender; @Before - public void before() throws Exception { + public void before() { listAppender = context.getListAppender("List"); } @@ -84,13 +82,12 @@ public void testLog4j2Only() throws InterruptedException { final Object[] originalArgs = Arrays.copyOf(args, args.length); listAppender.countDownLatch = new CountDownLatch(1); - ((ExtendedLogger)log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); + ((ExtendedLogger) log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); listAppender.countDownLatch.await(1, TimeUnit.SECONDS); Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); - ((ExtendedLogger)log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); + ((ExtendedLogger) log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); } - -} \ No newline at end of file +} diff --git a/log4j-core/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 similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java index 57d92478279..4e69ab7085a 100644 --- a/log4j-core/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 @@ -1,33 +1,32 @@ /* * 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.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.LogManager; -import org.apache.logging.log4j.categories.AsyncLoggers; import org.apache.logging.log4j.core.config.ConfigurationFactory; +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.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.test.appender.ListAppender; import org.apache.logging.log4j.util.Strings; import org.junit.AfterClass; import org.junit.Assert; @@ -48,8 +47,7 @@ public class Log4j2Jira1688Test { @BeforeClass public static void beforeClass() { - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j-list.xml"); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "log4j-list.xml"); } @AfterClass @@ -59,10 +57,11 @@ public static void afterClass() { @Rule public LoggerContextRule context = new LoggerContextRule("log4j-list.xml"); + private ListAppender listAppender; @Before - public void before() throws Exception { + public void before() { listAppender = context.getListAppender("List"); } @@ -82,13 +81,12 @@ public void testLog4j2Only() throws InterruptedException { final Object[] originalArgs = Arrays.copyOf(args, args.length); listAppender.countDownLatch = new CountDownLatch(1); - ((ExtendedLogger)log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); + ((ExtendedLogger) log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); listAppender.countDownLatch.await(1, TimeUnit.SECONDS); Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); - ((ExtendedLogger)log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); + ((ExtendedLogger) log4JLogger).logIfEnabled("test", Level.ERROR, null, "test {}", args); Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); } - -} \ No newline at end of file +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..57734ed983e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.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.logging.log4j.core.async; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +import com.lmax.disruptor.dsl.Disruptor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Stack; +import java.util.concurrent.CountDownLatch; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.jmx.RingBufferAdmin; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.ReflectionUtil; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.Timeout.ThreadMode; + +/** + * Tests queue full scenarios abstract superclass. + */ +@Tag("async") +@UsingStatusListener +@Timeout(value = 5, unit = SECONDS, threadMode = ThreadMode.SEPARATE_THREAD) +public abstract class QueueFullAbstractTest { + + protected static final String APPENDER_NAME = "Blocking"; + protected static final int BUFFER_COUNT = 128; + protected static final int MESSAGE_COUNT = BUFFER_COUNT + 2; + protected static final Logger LOGGER = StatusLogger.getLogger(); + + protected static class Unlocker extends Thread { + + final CountDownLatch countDownLatch; + final BlockingAppender blockingAppender; + + Unlocker(final CountDownLatch countDownLatch, final BlockingAppender blockingAppender) { + this.countDownLatch = countDownLatch; + this.blockingAppender = blockingAppender; + } + + @Override + public void run() { + try { + countDownLatch.await(); + LOGGER.info("Unlocker activated. Sleeping 500 millis before taking action..."); + Thread.sleep(500); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } + LOGGER.info("Unlocker signalling BlockingAppender to proceed..."); + blockingAppender.countDownLatch.countDown(); + } + } + + protected void testNormalQueueFullKeepsMessagesInOrder( + final LoggerContext ctx, final BlockingAppender blockingAppender) throws Exception { + checkConfig(ctx); + final Logger logger = ctx.getLogger(getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + final Unlocker unlocker = new Unlocker(new CountDownLatch(MESSAGE_COUNT - 1), blockingAppender); + unlocker.start(); + asyncTest(logger, unlocker, blockingAppender); + unlocker.join(); + } + + protected void checkConfig(final LoggerContext ctx) throws Exception {} + + protected static void asyncTest( + final Logger logger, final Unlocker unlocker, final BlockingAppender blockingAppender) { + for (int i = 0; i < MESSAGE_COUNT; i++) { + LOGGER.info( + "Test logging message {}. Ring buffer capacity was {}, countdown latch was {}.", + i, + asyncRemainingCapacity(logger), + unlocker.countDownLatch.getCount()); + unlocker.countDownLatch.countDown(); + final String param = "I'm innocent"; + logger.info("Logging innocent object #{}: {}", i, param); + } + LOGGER.info( + "Waiting for message delivery: blockingAppender.logEvents.count={}.", + blockingAppender.logEvents.size()); + while (blockingAppender.logEvents.size() < MESSAGE_COUNT) { + Thread.yield(); + } + LOGGER.info( + "All {} message have been delivered: blockingAppender.logEvents.count={}.", + MESSAGE_COUNT, + blockingAppender.logEvents.size()); + + final Stack actual = transform(blockingAppender.logEvents); + for (int i = 0; i < MESSAGE_COUNT; i++) { + assertThat(actual.pop()).isEqualTo("Logging innocent object #%d: I'm innocent", i); + } + assertThat(actual).isEmpty(); + } + + static Stack transform(final List logEvents) { + final List filtered = new ArrayList<>(logEvents.size()); + for (final LogEvent event : logEvents) { + filtered.add(event.getMessage().getFormattedMessage()); + } + Collections.reverse(filtered); + final Stack result = new Stack<>(); + result.addAll(filtered); + return result; + } + + static long asyncRemainingCapacity(final Logger logger) { + if (logger instanceof AsyncLogger) { + try { + final Field f = field(AsyncLogger.class, "loggerDisruptor"); + return ((AsyncLoggerDisruptor) f.get(logger)) + .getDisruptor() + .getRingBuffer() + .remainingCapacity(); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } else { + final LoggerConfig loggerConfig = ((org.apache.logging.log4j.core.Logger) logger).get(); + if (loggerConfig instanceof AsyncLoggerConfig) { + try { + final Object delegate = + field(AsyncLoggerConfig.class, "delegate").get(loggerConfig); + return ((Disruptor) field(AsyncLoggerConfigDisruptor.class, "disruptor") + .get(delegate)) + .getRingBuffer() + .remainingCapacity(); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } else { + final Appender async = loggerConfig.getAppenders().get("async"); + if (async instanceof AsyncAppender) { + return ((AsyncAppender) async).getQueueCapacity(); + } + } + } + throw new IllegalStateException("Neither Async Loggers nor AsyncAppender are configured"); + } + + protected static Field field(final Class c, final String name) throws NoSuchFieldException { + final Field f = c.getDeclaredField(name); + ReflectionUtil.makeAccessible(f); + return f; + } + + protected static void assertAsyncAppender(final LoggerContext ctx) { + assertThat(ctx).isNotInstanceOf(AsyncLoggerContext.class); + + final Configuration config = ctx.getConfiguration(); + assertThat(config).isNotNull(); + assertThat(config.getRootLogger()).isNotInstanceOf(AsyncLoggerConfig.class); + + final Collection appenders = + config.getRootLogger().getAppenders().values(); + assertThat(appenders).hasSize(1).allMatch(AsyncAppender.class::isInstance); + } + + protected static void assertAsyncLogger(final LoggerContext ctx, final int expectedBufferSize) { + assertThat(ctx).isInstanceOf(AsyncLoggerContext.class); + final RingBufferAdmin ringBufferAdmin = ((AsyncLoggerContext) ctx).createRingBufferAdmin(); + assertThat(ringBufferAdmin.getRemainingCapacity()).isEqualTo(expectedBufferSize); + + final Configuration config = ctx.getConfiguration(); + assertThat(config).isNotNull(); + assertThat(config.getRootLogger()).isNotInstanceOf(AsyncLoggerConfig.class); + } + + protected static void assertAsyncLoggerConfig(final LoggerContext ctx, final int expectedBufferSize) + throws ReflectiveOperationException { + assertThat(ctx).isNotInstanceOf(AsyncLoggerContext.class); + + final Configuration config = ctx.getConfiguration(); + assertThat(config).isNotNull(); + assertThat(config.getRootLogger()).isInstanceOf(AsyncLoggerConfig.class); + final AsyncLoggerConfigDisruptor disruptor = (AsyncLoggerConfigDisruptor) config.getAsyncLoggerConfigDelegate(); + final Field sizeField = field(AsyncLoggerConfigDisruptor.class, "ringBufferSize"); + assertThat(sizeField.get(disruptor)).isEqualTo(expectedBufferSize); + } + + protected static void assertFormatMessagesInBackground() { + assertThat(Constants.FORMAT_MESSAGES_IN_BACKGROUND).isTrue(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.java new file mode 100644 index 00000000000..43329dc1eae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.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.async; + +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.junit.jupiter.api.Test; + +/** + * Tests queue full scenarios with AsyncAppender. + */ +public class QueueFullAsyncAppender1Test extends QueueFullAbstractTest { + + @Override + @Test + @LoggerContextSource + protected void testNormalQueueFullKeepsMessagesInOrder( + final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) throws Exception { + super.testNormalQueueFullKeepsMessagesInOrder(ctx, blockingAppender); + } + + @Override + protected void checkConfig(final LoggerContext ctx) { + assertAsyncAppender(ctx); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.java new file mode 100644 index 00000000000..4eab6286a09 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppender2Test.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.async; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.test.junit.SetTestProperty; + +/** + * Needs to be a separate class since {@link org.apache.logging.log4j.core.util.Constants#FORMAT_MESSAGES_IN_BACKGROUND} + * is immutable. + */ +@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true") +public class QueueFullAsyncAppender2Test extends QueueFullAsyncAppender1Test { + + @Override + protected void checkConfig(final LoggerContext ctx) { + super.checkConfig(ctx); + assertFormatMessagesInBackground(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.java new file mode 100644 index 00000000000..fe3f42e043f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger1Test.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.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.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests queue full scenarios with pure AsyncLoggers (all loggers async). + */ +@SetTestProperty( + key = Constants.LOG4J_CONTEXT_SELECTOR, + value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") +@SetTestProperty(key = "log4j2.asyncLoggerRingBufferSize", value = "128") +@Tag(Tags.ASYNC_LOGGERS) +public class QueueFullAsyncLogger1Test extends QueueFullAbstractTest { + + @Override + @Test + @LoggerContextSource + protected void testNormalQueueFullKeepsMessagesInOrder( + final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) throws Exception { + super.testNormalQueueFullKeepsMessagesInOrder(ctx, blockingAppender); + } + + @Override + protected void checkConfig(final LoggerContext ctx) { + assertAsyncLogger(ctx, 128); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.java new file mode 100644 index 00000000000..fbfce3951bb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger2Test.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.async; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Tag; + +/** + * Needs to be a separate class since {@link org.apache.logging.log4j.core.util.Constants#FORMAT_MESSAGES_IN_BACKGROUND} + * is immutable. + */ +@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true") +@Tag(Tags.ASYNC_LOGGERS) +public class QueueFullAsyncLogger2Test extends QueueFullAsyncLogger1Test { + + @Override + protected void checkConfig(final LoggerContext ctx) { + super.checkConfig(ctx); + assertFormatMessagesInBackground(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.java new file mode 100644 index 00000000000..10d45ac8a70 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLogger3Test.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.async; + +import static org.apache.logging.log4j.core.GcHelper.awaitGarbageCollection; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +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.junit.Tags; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests queue full scenarios with pure AsyncLoggers (all loggers async). + */ +@SetTestProperty( + key = Constants.LOG4J_CONTEXT_SELECTOR, + value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") +@SetTestProperty(key = "log4j2.asyncLoggerRingBufferSize", value = "128") +@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true") +@SetTestProperty(key = "log4j2.asyncQueueFullPolicy", value = "Discard") +@Tag(Tags.ASYNC_LOGGERS) +class QueueFullAsyncLogger3Test extends QueueFullAbstractTest { + + @Override + protected void checkConfig(final LoggerContext ctx) { + assertAsyncLogger(ctx, 128); + assertFormatMessagesInBackground(); + assertThat(AsyncQueueFullPolicyFactory.create()).isInstanceOf(DiscardingAsyncQueueFullPolicy.class); + } + + @Test + @LoggerContextSource + void discarded_messages_should_be_garbage_collected( + final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) + throws InterruptedException { + awaitGarbageCollection(() -> { + checkConfig(ctx); + final Logger logger = ctx.getLogger(getClass()); + blockingAppender.logEvents = null; + blockingAppender.countDownLatch = new CountDownLatch(1); + final List messages = IntStream.range(0, 200) + .mapToObj(messageIndex -> new SimpleMessage("message " + messageIndex)) + .collect(Collectors.toList()); + messages.forEach(logger::info); + blockingAppender.countDownLatch.countDown(); + return messages; + }); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.java new file mode 100644 index 00000000000..e797792ddb1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.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.async; + +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.junit.Tags; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests queue full scenarios with AsyncLoggers in configuration. + */ +@SetTestProperty(key = "log4j2.asyncLoggerConfigRingBufferSize", value = "128") +@Tag(Tags.ASYNC_LOGGERS) +public class QueueFullAsyncLoggerConfig1Test extends QueueFullAbstractTest { + + @Override + @Test + @LoggerContextSource + protected void testNormalQueueFullKeepsMessagesInOrder( + final LoggerContext ctx, final @Named(APPENDER_NAME) BlockingAppender blockingAppender) throws Exception { + super.testNormalQueueFullKeepsMessagesInOrder(ctx, blockingAppender); + } + + @Override + protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException { + assertAsyncLoggerConfig(ctx, 128); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.java new file mode 100644 index 00000000000..a88d4568bd0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig2Test.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 org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.Tags; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Tag; + +/** + * Tests queue full scenarios with AsyncLoggers in configuration. + */ +@SetTestProperty(key = "log4j2.formatMsgAsync", value = "true") +@Tag(Tags.ASYNC_LOGGERS) +public class QueueFullAsyncLoggerConfig2Test extends QueueFullAsyncLoggerConfig1Test { + + @Override + protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException { + super.checkConfig(ctx); + assertFormatMessagesInBackground(); + } +} 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..5377040565c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java @@ -0,0 +1,471 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +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; +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.FixedPreciseClock; +import org.apache.logging.log4j.core.util.Clock; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.core.util.NanoClock; +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.test.junit.SerialUtil; +import org.apache.logging.log4j.util.StringMap; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests the RingBufferLogEvent class. + */ +@Tag("async") +class RingBufferLogEventTest { + + @Test + void testToImmutable() { + final LogEvent logEvent = new RingBufferLogEvent(); + assertThat(logEvent).isNotSameAs(logEvent.toImmutable()); + } + + /** + * Reproduces LOG4J2-2816. + */ + @Test + void testIsPopulated() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + assertThat(evt.isPopulated()).isFalse(); + + 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)); + + assertThat(evt.isPopulated()).isTrue(); + + evt.clear(); + + assertThat(evt.isPopulated()).isFalse(); + } + + @Test + 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)); + assertThat(evt.getLevel()).isEqualTo(Level.OFF); + } + + @Test + 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)); + assertThat(evt.getMessage()).isNotNull(); + } + + @Test + 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)); + assertThat(evt.getTimeMillis()).isEqualTo(123); + assertThat(evt.getInstant().getNanoOfMillisecond()).isEqualTo(456); + } + + @Test + void testSerializationDeserialization() { + 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 = ThreadContext.getImmutableStack(); + 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 other = SerialUtil.deserialize(SerialUtil.serialize(evt)); + assertThat(other.getLoggerName()).as("Logger name").isEqualTo(loggerName); + assertThat(other.getMarker()).as("Marker").isEqualTo(marker); + assertThat(other.getLoggerFqcn()) + .as("Fully qualified class name of logger implementation") + .isEqualTo(fqcn); + assertThat(other.getLevel()).as("Log event level").isEqualTo(level); + assertThat(other.getMessage()).as("Log event message").isEqualTo(data); + assertThat(other.getThrown()).as("Thrown exception").isNull(); + assertThat(other.getThrownProxy()) + .as("Serialization proxy for thrown exception") + .isEqualTo(new ThrowableProxy(t)); + assertThat(other.getContextData()).as("Context data map").isEqualTo(evt.getContextData()); + assertThat(other.getContextStack()).as("Context data stack").isEqualTo(contextStack); + assertThat(other.getThreadName()).as("Thread name").isEqualTo(threadName); + assertThat(other.getSource()).as("Log event location").isEqualTo(location); + assertThat(other.getTimeMillis()).as("Log event timestamp in millis").isEqualTo(12345); + assertThat(other.getInstant().getNanoOfMillisecond()) + .as("Log event timestamp in nanos of millis") + .isEqualTo(678); + } + + @SuppressWarnings("deprecation") + @Test + void testToImmutableReturnsCopy() { + 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.toImmutable(); + assertThat(actual.getLoggerName()).isEqualTo(evt.getLoggerName()); + assertThat(actual.getMarker()).isEqualTo(evt.getMarker()); + assertThat(actual.getLoggerFqcn()).isEqualTo(evt.getLoggerFqcn()); + assertThat(actual.getLevel()).isEqualTo(evt.getLevel()); + assertThat(actual.getMessage()).isEqualTo(evt.getMessage()); + assertThat(actual.getThrown()).isEqualTo(evt.getThrown()); + assertThat(actual.getContextMap()).isEqualTo(evt.getContextMap()); + assertThat(actual.getContextData()).isEqualTo(evt.getContextData()); + assertThat(actual.getContextStack()).isEqualTo(evt.getContextStack()); + assertThat(actual.getThreadName()).isEqualTo(evt.getThreadName()); + assertThat(actual.getTimeMillis()).isEqualTo(evt.getTimeMillis()); + assertThat(actual.getInstant().getNanoOfMillisecond()) + .isEqualTo(evt.getInstant().getNanoOfMillisecond()); + assertThat(actual.getSource()).isEqualTo(evt.getSource()); + assertThat(actual.getThrownProxy()).isEqualTo(evt.getThrownProxy()); + } + + @Test + void testToImmutableRetainsParametersAndFormat() { + 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.toImmutable().getMessage(); + assertThat(actual.getFormat()).isEqualTo("Hello {}!"); + assertThat(actual.getParameters()).isEqualTo(new String[] {"World"}); + assertThat(actual.getFormattedMessage()).isEqualTo("Hello World!"); + } finally { + ReusableMessageFactory.release(message); + } + } + + @Test + 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).isSameAs(memento2); + } finally { + ReusableMessageFactory.release(message); + } + } + + @Test + void testMessageTextNeverThrowsNpe() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + assertThatCode(evt::getFormattedMessage).doesNotThrowAnyException(); + } + + @Test + void testForEachParameterNothingSet() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + assertThatCode(() -> evt.forEachParameter( + (parameter, parameterIndex, state) -> fail("Should not have been called"), null)) + .doesNotThrowAnyException(); + } + + /** + * Reproduces #2234. + */ + @Test + void testGettersAndClear() { + + // Create mock fields + final long salt = (long) (Math.random() * 1_000L); + final AsyncLogger asyncLogger = mock(AsyncLogger.class); + final String loggerName = "LoggerName-" + salt; + final Marker marker = MarkerManager.getMarker("marker-" + salt); + final String fqcn = "a.b.c_" + salt; + final Level level = Level.TRACE; + final Message message = mock(Message.class); + final Throwable throwable = mock(Throwable.class); + final StringMap contextData = mock(StringMap.class); + final ContextStack contextStack = mock(ContextStack.class); + final String threadName = "threadName-" + salt; + final StackTraceElement location = new RuntimeException().getStackTrace()[0]; + + // Create the log event + final Clock clock = mock(Clock.class); + final NanoClock nanoClock = mock(NanoClock.class); + final RingBufferLogEvent event = new RingBufferLogEvent(); + event.setValues( + asyncLogger, + loggerName, + marker, + fqcn, + level, + message, + throwable, + contextData, + contextStack, + -1, + threadName, + -1, + location, + clock, + nanoClock); + + // Verify getters + assertThat(event.getLoggerName()).isSameAs(loggerName); + assertThat(event.getMarker()).isSameAs(marker); + assertThat(event.getLoggerFqcn()).isSameAs(fqcn); + assertThat(event.getLevel()).isSameAs(level); + assertThat(event.getMessage()).isSameAs(message); + assertThat(event.getThrowable()).isSameAs(throwable); + assertThat(event.getContextData()).isSameAs(contextData); + assertThat(event.getContextStack()).isSameAs(contextStack); + assertThat(event.getThreadName()).isSameAs(threadName); + assertThat(event.getSource()).isSameAs(location); + + // Verify clear + event.clear(); + assertThat(event.getLoggerName()).isNull(); + assertThat(event.getMarker()).isNull(); + assertThat(event.getLoggerFqcn()).isNull(); + assertThat(event.getLevel()).isEqualTo(Level.OFF); + verify(message).getFormattedMessage(); + assertThat(event.getMessage()) + .isNotSameAs(message) + .extracting(Message::getFormattedMessage, as(InstanceOfAssertFactories.STRING)) + .isEmpty(); + assertThat(event.getThrowable()).isNull(); + verify(contextData).isFrozen(); + verify(contextData).clear(); + assertThat(event.getContextData()).isSameAs(contextData); + assertThat(event.getContextStack()).isNull(); + assertThat(event.getThreadName()).isNull(); + assertThat(event.getSource()).isNull(); + + // Verify interaction exhaustion + verifyNoMoreInteractions(asyncLogger, message, throwable, contextData, contextStack); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AbstractConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AbstractConfigurationTest.java new file mode 100644 index 00000000000..64ce9ac3fc6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AbstractConfigurationTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.core.filter.ScriptFilter; +import org.apache.logging.log4j.core.lookup.Interpolator; +import org.apache.logging.log4j.core.lookup.InterpolatorTest; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.Issue; + +@SetTestProperty(key = "log4j2.scriptEnableLanguages", value = "groovy") +class AbstractConfigurationTest { + + @Test + void propertiesCanComeLast() { + final Configuration config = new TestConfiguration(null, Collections.singletonMap("console.name", "CONSOLE")); + config.initialize(); + final StrSubstitutor substitutor = config.getStrSubstitutor(); + assertThat(substitutor.replace("${console.name}")) + .as("No interpolation for '${console.name}'") + .isEqualTo("CONSOLE"); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + @Issue("https://github.com/apache/logging-log4j2/issues/2309") + void substitutorHasConfigurationAndLoggerContext(final boolean hasProperties) { + final LoggerContext context = mock(LoggerContext.class); + final Configuration config = new TestConfiguration(context, hasProperties ? Collections.emptyMap() : null); + config.initialize(); + final Interpolator runtime = (Interpolator) config.getStrSubstitutor().getVariableResolver(); + final Interpolator configTime = + (Interpolator) config.getConfigurationStrSubstitutor().getVariableResolver(); + for (final Interpolator interpolator : Arrays.asList(runtime, configTime)) { + assertThat(InterpolatorTest.getConfiguration(interpolator)).isEqualTo(config); + assertThat(InterpolatorTest.getLoggerContext(interpolator)).isEqualTo(context); + } + } + + @Test + @LoggerContextSource("log4j2-script-order-test.xml") + void scriptRefShouldBeResolvedWhenScriptsElementIsLast(final Configuration config) { + assertThat(config.getFilter()) + .as("Top-level filter should be a CompositeFilter") + .isInstanceOf(CompositeFilter.class); + final CompositeFilter compositeFilter = (CompositeFilter) config.getFilter(); + + assertThat(compositeFilter.getFilters()) + .as("CompositeFilter should contain one filter") + .hasSize(1); + final ScriptFilter scriptFilter = + (ScriptFilter) compositeFilter.getFilters().get(0); + + assertThat(scriptFilter).isNotNull(); + assertThat(scriptFilter.toString()) + .as("Script name should match the one in the config") + .isEqualTo("GLOBAL_FILTER"); + } + + private static class TestConfiguration extends AbstractConfiguration { + + private final Map map; + + public TestConfiguration(final LoggerContext context, final Map map) { + super(context, ConfigurationSource.NULL_SOURCE); + this.map = map; + } + + @Override + public void setup() { + // Nodes + final Node loggers = newNode(rootNode, "Loggers"); + rootNode.getChildren().add(loggers); + + final Node rootLogger = newNode(loggers, "Root"); + rootLogger.getAttributes().put("level", "INFO"); + loggers.getChildren().add(rootLogger); + + if (map != null) { + final Node properties = newNode(rootNode, "Properties"); + rootNode.getChildren().add(properties); + + for (final Entry entry : map.entrySet()) { + final Node property = newNode(properties, "Property"); + property.getAttributes().put("name", entry.getKey()); + property.getAttributes().put("value", entry.getValue()); + properties.getChildren().add(property); + } + } + } + + private Node newNode(final Node parent, final String name) { + return new Node(parent, name, pluginManager.getPluginType(name)); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..e99cf2fbc25 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class AdvertiserTest { + + private static final String CONFIG = "log4j-advertiser.xml"; + private static final String STATUS_LOG = "target/status.log"; + + @BeforeAll + 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(); + if (config instanceof XmlConfiguration) { + final String name = config.getName(); + if (name == null || !name.equals("XMLConfigTest")) { + ctx.reconfigure(); + } + } else { + ctx.reconfigure(); + } + } + + @AfterAll + 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) { + boolean foundFile1 = false; + boolean foundFile2 = false; + boolean foundSocket1 = false; + boolean foundSocket2 = false; + for (final Map entry : entries.values()) { + if (foundFile1 && foundFile2 && foundSocket1 && foundSocket2) { + break; + } + if (entry.get("name").equals("File1")) { + foundFile1 = true; + } + if (entry.get("name").equals("File2")) { + foundFile2 = true; + } + if (entry.get("name").equals("Socket1")) { + foundSocket1 = true; + } + if (entry.get("name").equals("Socket2")) { + foundSocket2 = true; + } + } + assertTrue(foundFile1, "Entries for File1 appender do not exist"); + assertFalse(foundFile2, "Entries for File2 appender exist"); + assertTrue(foundSocket1, "Entries for Socket1 appender do not exist"); + assertFalse(foundSocket2, "Entries for Socket2 appender exist"); + } + + @Test + void testAdvertisementsFound() { + verifyExpectedEntriesAdvertised(InMemoryAdvertiser.getAdvertisedEntries()); + } + + @Test + void testAdvertisementsRemovedOnConfigStop() { + verifyExpectedEntriesAdvertised(InMemoryAdvertiser.getAdvertisedEntries()); + + final LoggerContext ctx = LoggerContext.getContext(); + ctx.stop(); + + final Map> entries = InMemoryAdvertiser.getAdvertisedEntries(); + assertTrue(entries.isEmpty(), "Entries found: " + entries); + + // reconfigure for subsequent testing + ctx.start(); + } + + @Test + void testAdvertisementsAddedOnReconfigAfterStop() { + verifyExpectedEntriesAdvertised(InMemoryAdvertiser.getAdvertisedEntries()); + + final LoggerContext ctx = LoggerContext.getContext(); + ctx.stop(); + + final Map> entries = InMemoryAdvertiser.getAdvertisedEntries(); + assertTrue(entries.isEmpty(), "Entries found: " + entries); + + ctx.start(); + + verifyExpectedEntriesAdvertised(InMemoryAdvertiser.getAdvertisedEntries()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..70abd92bc1b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.test.appender.FailOnceAppender; +import org.junit.jupiter.api.Test; + +/** + * Tests the AppenderControlArraySet class.. + */ +class AppenderControlArraySetTest { + + @Test + void testInitiallyEmpty() { + assertTrue(new AppenderControlArraySet().isEmpty()); + assertEquals(0, new AppenderControlArraySet().get().length); + } + + private AppenderControl createControl(final String name) { + final Appender appender = FailOnceAppender.createAppender(name, null); + return new AppenderControl(appender, Level.INFO, null); + } + + @Test + void testAddMakesNonEmpty() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + assertTrue(set.isEmpty()); + set.add(createControl("A")); + assertFalse(set.isEmpty()); + } + + @Test + void testAddReturnsTrueIfSuccessfullyAdded() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + assertTrue(set.add(createControl("A"))); + assertTrue(set.add(createControl("B"))); + assertTrue(set.add(createControl("C"))); + } + + @Test + void testAddDoesNotAppendersWithSameName() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + final AppenderControl[] controls = new AppenderControl[] { + createControl("A"), createControl("B"), createControl("B"), createControl("B"), createControl("A") + }; + for (final AppenderControl ctl : controls) { + set.add(ctl); + } + assertEquals(2, set.get().length); + assertSame(controls[0], set.get()[0]); + assertSame(controls[1], set.get()[1]); + } + + @Test + void testAddReturnsFalseIfAlreadyInSet() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + assertTrue(set.add(createControl("A"))); + assertTrue(set.add(createControl("B"))); + assertFalse(set.add(createControl("B"))); + assertFalse(set.add(createControl("B"))); + assertFalse(set.add(createControl("A"))); + assertEquals(2, set.get().length); + } + + @Test + void testRemoveRemovesItemFromSet() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + set.add(createControl("A")); + set.add(createControl("B")); + set.add(createControl("C")); + set.add(createControl("D")); + assertEquals(4, set.get().length); + + set.remove("B"); + assertEquals(3, set.get().length); + final AppenderControl[] three = set.get(); + assertEquals("A", three[0].getAppenderName()); + assertEquals("C", three[1].getAppenderName()); + assertEquals("D", three[2].getAppenderName()); + + set.remove("C"); + assertEquals(2, set.get().length); + final AppenderControl[] two = set.get(); + assertEquals("A", two[0].getAppenderName()); + assertEquals("D", two[1].getAppenderName()); + + set.remove("A"); + assertEquals(1, set.get().length); + final AppenderControl[] one = set.get(); + assertEquals("D", one[0].getAppenderName()); + + set.remove("D"); + assertTrue(set.isEmpty()); + } + + @Test + void testRemoveReturnsRemovedItem() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + final AppenderControl[] controls = + new AppenderControl[] {createControl("A"), createControl("B"), createControl("C"), createControl("D")}; + for (final AppenderControl ctl : controls) { + set.add(ctl); + } + assertEquals(controls.length, set.get().length); + + final AppenderControl b = set.remove("B"); + assertSame(controls[1], b); + + final AppenderControl c = set.remove("C"); + assertSame(controls[2], c); + } + + @Test + void testAsMap() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + final AppenderControl[] controls = + new AppenderControl[] {createControl("A"), createControl("B"), createControl("C"), createControl("D")}; + for (final AppenderControl ctl : controls) { + set.add(ctl); + } + final Map expected = new HashMap<>(); + for (final AppenderControl ctl : controls) { + expected.put(ctl.getAppenderName(), ctl.getAppender()); + } + assertEquals(expected, set.asMap()); + } + + @Test + void testClearRemovesAllItems() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + set.add(createControl("A")); + set.add(createControl("B")); + set.add(createControl("C")); + assertFalse(set.isEmpty()); + + set.clear(); + assertTrue(set.isEmpty()); + } + + @Test + void testClearReturnsAllItems() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + final AppenderControl[] controls = + new AppenderControl[] {createControl("A"), createControl("B"), createControl("C")}; + for (final AppenderControl ctl : controls) { + set.add(ctl); + } + assertEquals(3, set.get().length); + final AppenderControl[] previous = set.clear(); + assertArrayEquals(previous, controls); + } + + @Test + void testIsEmptyMeansZeroLengthArray() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + assertTrue(set.isEmpty()); + assertEquals(0, set.get().length); + } + + @Test + void testGetReturnsAddedItems() { + final AppenderControlArraySet set = new AppenderControlArraySet(); + final AppenderControl[] controls = + new AppenderControl[] {createControl("A"), createControl("B"), createControl("C")}; + for (final AppenderControl ctl : controls) { + set.add(ctl); + } + assertEquals(3, set.get().length); + assertArrayEquals(controls, set.get()); + } +} 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..ff3440ceb59 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationMissingTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class CompositeConfigurationMissingTest { + + @BeforeAll + static void beforeClass() { + System.setProperty( + "log4j2.configurationFile", "classpath:log4j-comp-logger-root.xml,log4j-does-not-exist.json"); + } + + @Test + void testMissingConfig() { + final 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"); + assertNotNull(cat2, "cat2"); + assertEquals(Level.DEBUG, cat2.getLevel(), "Expected cat2 log level to be INFO"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..69956c1c845 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +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; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; +import org.apache.logging.log4j.core.filter.RegexFilter; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.util.Throwables; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class CompositeConfigurationTest { + /* + @Test + public void compositeConfigurationUsed() { + final LoggerContextRule lcr = new LoggerContextRule( + "classpath:log4j-comp-appender.xml,log4j-comp-appender.json"); + Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + assertTrue(lcr.getConfiguration() instanceof CompositeConfiguration); + } + }; + runTest(lcr, test); + } + + @Test + public void compositeProperties() { + final LoggerContextRule lcr = new LoggerContextRule( + "classpath:log4j-comp-properties.xml,log4j-comp-properties.json"); + Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + assertEquals("json", config.getStrSubstitutor().replace("${propertyShared}")); + assertEquals("xml", config.getStrSubstitutor().replace("${propertyXml}")); + assertEquals("json", config.getStrSubstitutor().replace("${propertyJson}")); + } + }; + runTest(lcr, test); + } + + @Test + public void compositeAppenders() { + final LoggerContextRule lcr = new LoggerContextRule( + "classpath:log4j-comp-appender.xml,log4j-comp-appender.json"); + Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + Map appender = config.getAppenders(); + assertEquals(3, appender.size()); + assertTrue(appender.get("STDOUT") instanceof ConsoleAppender); + assertTrue(appender.get("File") instanceof FileAppender); + assertTrue(appender.get("Override") instanceof RollingFileAppender); + } + }; + runTest(lcr, test); + } + */ + @Test + public void compositeLogger() { + final LoggerContextRule lcr = new LoggerContextRule("classpath:log4j-comp-logger.xml,log4j-comp-logger.json"); + final Statement test = new Statement() { + @Override + public void evaluate() { + final CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + Map appendersMap = config.getLogger("cat1").getAppenders(); + assertEquals( + "Expected 2 Appender references for cat1 but got " + appendersMap.size(), + 2, + appendersMap.size()); + assertTrue(appendersMap.get("STDOUT") instanceof ConsoleAppender); + + final Filter loggerFilter = config.getLogger("cat1").getFilter(); + assertTrue(loggerFilter instanceof RegexFilter); + assertEquals(Filter.Result.DENY, loggerFilter.getOnMatch()); + + appendersMap = config.getLogger("cat2").getAppenders(); + assertEquals( + "Expected 1 Appender reference for cat2 but got " + appendersMap.size(), + 1, + appendersMap.size()); + assertTrue(appendersMap.get("File") instanceof FileAppender); + + appendersMap = config.getLogger("cat3").getAppenders(); + assertEquals( + "Expected 1 Appender reference for cat3 but got " + appendersMap.size(), + 1, + appendersMap.size()); + assertTrue(appendersMap.get("File") instanceof FileAppender); + + appendersMap = config.getRootLogger().getAppenders(); + assertEquals( + "Expected 2 Appender references for the root logger but got " + appendersMap.size(), + 2, + 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(), + ConfigurationSource.COMPOSITE_SOURCE, + config.getConfigurationSource()); + } + }; + runTest(lcr, test); + } + + @Test + public void testAttributeCheckWhenMergingConfigurations() { + final LoggerContextRule lcr = + new LoggerContextRule("classpath:log4j-comp-root-loggers.xml,log4j-comp-logger.json"); + final Statement test = new Statement() { + @Override + public void evaluate() { + try { + final CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + Assert.assertNotNull(config); + } catch (final NullPointerException e) { + fail("Should not throw NullPointerException when there are different nodes."); + } + } + }; + runTest(lcr, test); + } + + @Test + public void testAttributeMergeForLoggers() { + final LoggerContextRule lcr = + new LoggerContextRule("classpath:log4j-comp-logger-root.xml,log4j-comp-logger-attr-override.json"); + final Statement test = new Statement() { + @Override + public void evaluate() { + final CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + // Test for Root log level override + assertEquals( + "Expected Root logger log level to be WARN", + Level.WARN, + config.getRootLogger().getLevel()); + + // Test for cat2 level override + final LoggerConfig cat2 = config.getLogger("cat2"); + assertEquals("Expected cat2 log level to be INFO", Level.INFO, cat2.getLevel()); + + // Test for cat2 additivity override + assertTrue("Expected cat2 additivity to be true", cat2.isAdditive()); + + // 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 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() { + 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("classpath:log4j-comp-logger-ref.xml,log4j-comp-logger-ref.json"); + final Statement test = new Statement() { + @Override + public void evaluate() { + final CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + + final List appenderRefList = + config.getLogger("cat1").getAppenderRefs(); + final AppenderRef appenderRef = getAppenderRef(appenderRefList, "STDOUT"); + assertTrue( + "Expected cat1 STDOUT appenderRef to have a regex filter", + appenderRef.getFilter() != null && appenderRef.getFilter() instanceof RegexFilter); + } + }; + runTest(lcr, test); + } + + private AppenderRef getAppenderRef(final List appenderRefList, final String refName) { + for (final AppenderRef ref : appenderRefList) { + if (ref.getRef().equalsIgnoreCase(refName)) { + return ref; + } + } + return null; + } + /* + @Test + public void overrideFilter() { + final LoggerContextRule lcr = new LoggerContextRule("classpath:log4j-comp-filter.xml,log4j-comp-filter.json"); + Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + CompositeConfiguration config = (CompositeConfiguration) lcr.getConfiguration(); + assertTrue(config.getFilter() instanceof CompositeFilter); + CompositeFilter filter = (CompositeFilter) config.getFilter(); + assertTrue(filter.getFiltersArray().length == 2); + } + }; + runTest(lcr, test); + } + + @Test + public void testReconfiguration() throws Exception { + final LoggerContextRule rule = + new LoggerContextRule("classpath:log4j-comp-reconfig.xml,log4j-comp-reconfig.properties"); + Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + final Configuration oldConfig = rule.getConfiguration(); + final org.apache.logging.log4j.Logger logger = rule.getLogger("LoggerTest"); + final int MONITOR_INTERVAL_SECONDS = 5; + final File file = new File("target/test-classes/log4j-comp-reconfig.properties"); + final long orig = file.lastModified(); + final long newTime = orig + 10000; + assertTrue("setLastModified should have succeeded.", file.setLastModified(newTime)); + TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); + for (int i = 0; i < 17; ++i) { + logger.debug("Reconfigure"); + } + int loopCount = 0; + Configuration newConfig; + do { + Thread.sleep(100); + newConfig = rule.getConfiguration(); + ++loopCount; + } while (newConfig == oldConfig && loopCount <= 5); + assertNotSame("Reconfiguration failed", newConfig, oldConfig); + } + }; + runTest(rule, test); + + } */ + + private void runTest(final LoggerContextRule rule, final Statement statement) { + try { + rule.apply( + statement, + Description.createTestDescription( + getClass(), + Thread.currentThread().getStackTrace()[1].getMethodName())) + .evaluate(); + } catch (final Throwable e) { + Throwables.rethrow(e); + } + } +} 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..f2d81f13472 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationFactoryTest.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.config; + +import static org.apache.logging.log4j.util.Unbox.box; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; +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; +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.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +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"; + + @TempLoggingDir + private static Path loggingPath; + + /** + * 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()); + assertInstanceOf(ThreadContextMapFilter.class, filterIterator.next()); + + final Appender appender = appenders.get(APPENDER_NAME); + assertInstanceOf(ConsoleAppender.class, appender); + 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 = loggingPath.resolve("test-xml.log"); + checkFileLogger(context, logFile); + } + + @Test + @LoggerContextSource("log4j-xinclude.xml") + void xinclude(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = loggingPath.resolve("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 = loggingPath.resolve("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 = loggingPath.resolve("test-yaml.log"); + checkFileLogger(context, logFile); + } + + @Test + @LoggerContextSource("log4j-test1.properties") + void properties(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = loggingPath.resolve("test-properties.log"); + checkFileLogger(context, logFile); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationPropertyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationPropertyTest.java new file mode 100644 index 00000000000..18813ce1d9f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationPropertyTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class ConfigurationPropertyTest { + + private LoggerContext loggerContext; + + @AfterEach + void afterEach() { + if (loggerContext != null) { + LogManager.shutdown(loggerContext); + } + System.clearProperty("log4j2.configurationFile"); + System.clearProperty("log4j.configurationFile"); + } + + @Test + void testInitializeFromSystemProperty() { + System.setProperty("log4j2.configurationFile", "src/test/resources/log4j-list.xml"); + loggerContext = (LoggerContext) LogManager.getContext(false); + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration, "Null configuration"); + final Appender app = configuration.getAppender("List"); + assertNotNull(app, " Could not locate List Appender"); + } +} 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..3015f4f6271 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.util.Objects.requireNonNull; +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 com.sun.management.UnixOperatingSystemMXBean; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ConfigurationSourceTest { + /** + * The path inside the jar created by {@link #prepareJarConfigURL} containing the configuration. + */ + public static final String PATH_IN_JAR = "/config/console.xml"; + + private static final String CONFIG_FILE = "/config/ConfigurationSourceTest.xml"; + + @TempDir + private Path tempDir; + + @Test + void testJira_LOG4J2_2770_byteArray() throws Exception { + final 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. + */ + @Test + void testNoJarFileLeak() throws Exception { + final Path jarFile = prepareJarConfigURL(tempDir); + final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); + final long expected = getOpenFileDescriptorCount(); + UrlConnectionFactory.createConnection(jarConfigURL).getInputStream().close(); + // This can only fail on UNIX + assertEquals(expected, getOpenFileDescriptorCount()); + // This can only fail on Windows + try { + Files.delete(jarFile); + } catch (IOException e) { + fail(e); + } + } + + @Test + void testLoadConfigurationSourceFromJarFile() throws Exception { + final Path jarFile = prepareJarConfigURL(tempDir); + final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); + final long expectedFdCount = getOpenFileDescriptorCount(); + ConfigurationSource configSource = ConfigurationSource.fromUri(jarConfigURL.toURI()); + assertNotNull(configSource); + assertEquals(jarConfigURL.toString(), configSource.getLocation()); + assertNull(configSource.getFile()); + assertTrue(configSource.getLastModified() > 0); + assertEquals(jarConfigURL, configSource.getURL()); + assertNotNull(configSource.getInputStream()); + configSource.getInputStream().close(); + + // This can only fail on UNIX + assertEquals(expectedFdCount, getOpenFileDescriptorCount()); + // This can only fail on Windows + try { + Files.delete(jarFile); + } catch (IOException e) { + fail(e); + } + } + + private long getOpenFileDescriptorCount() { + final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + if (os instanceof UnixOperatingSystemMXBean) { + return ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount(); + } + return 0L; + } + + public static Path prepareJarConfigURL(Path dir) throws IOException { + Path jarFile = dir.resolve("jarFile.jar"); + final Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + try (final OutputStream os = Files.newOutputStream(jarFile); + final JarOutputStream jar = new JarOutputStream(os, manifest); + final InputStream config = + requireNonNull(ConfigurationSourceTest.class.getResourceAsStream(CONFIG_FILE))) { + final JarEntry jarEntry = new JarEntry("config/console.xml"); + jar.putNextEntry(jarEntry); + IOUtils.copy(config, os); + } + return jarFile; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java new file mode 100644 index 00000000000..0a619bae9cb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java @@ -0,0 +1,513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.apache.logging.log4j.core.test.hamcrest.MapMatchers.hasSize; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.theInstance; +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.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 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.core.util.Constants; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "beanshell, Groovy, Javascript") +@Tag("functional") +class Configurator1Test { + + private static final String CONFIG_NAME = "TestConfigurator"; + + 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 + void cleanup() { + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + if (ctx != null) { + Configurator.shutdown(ctx); + ctx = null; + } + } + + @Test + void testInitialize_Name_PathName() { + ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-TestConfigurator.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 + void testInitialize_Name_ClassLoader_URI() { + ctx = Configurator.initialize( + "Test1", null, new File("target/test-classes/log4j2-TestConfigurator.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 + void testInitialize_InputStream_File() throws Exception { + final File file = new File("target/test-classes/log4j2-TestConfigurator.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 + void testInitialize_InputStream_Path() throws Exception { + final Path path = Paths.get("target/test-classes/log4j2-TestConfigurator.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 + void testInitialize_NullClassLoader_ConfigurationSourceWithInputStream_NoId() throws Exception { + final InputStream is = new FileInputStream("target/test-classes/log4j2-TestConfigurator.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 + void testInitialize_Name_LocationName() { + ctx = Configurator.initialize("Test1", "log4j2-TestConfigurator.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 + void testFromClassPathProperty() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "classpath:log4j2-TestConfigurator.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 + void testFromClassPathWithClassPathPrefix() { + ctx = Configurator.initialize("Test1", "classpath:log4j2-TestConfigurator.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 + void testFromClassPathWithClassLoaderPrefix() { + ctx = Configurator.initialize("Test1", "classloader:log4j2-TestConfigurator.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 + void testByName() { + ctx = Configurator.initialize("-TestConfigurator", 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 + void testReconfiguration() throws Exception { + final File file = new File("target/test-classes/log4j2-TestConfigurator.xml"); + assertTrue(file.setLastModified(System.currentTimeMillis() - 120000), "setLastModified should have succeeded."); + ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-TestConfigurator.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 < 100; ++i) { + logger.debug("Test message " + i); + } + + // Sleep and check + Thread.sleep(100); + if (is(theInstance(config)).matches(ctx.getConfiguration())) { + Thread.sleep(2000); + } + 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 + void testEnvironment() { + ctx = Configurator.initialize("-TestConfigurator", 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 List", map, hasKey("List")); + final Appender app = map.get("List"); + final Layout layout = app.getLayout(); + assertNotNull(layout, "Appender List does not have a Layout"); + assertThat("Appender List 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 + void testNoLoggers() { + 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 = "Configurator1Test.testNoLoggers"; + assertEquals(name, config.getName(), "Unexpected Configuration."); + } + + @Test + void testBadStatus() { + 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 + void testBadFilterParam() { + 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 + void testNoFilters() { + 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 + void testBadLayout() { + 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 + void testBadFileName() { + final StringBuilder dir = new StringBuilder("/VeryLongDirectoryName"); + + for (final String element : CHARS) { + dir.append(element); + dir.append(toRootUpperCase(element)); + } + final String value = FILESEP.equals("/") ? dir + "/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 + void testBuilder() { + 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 + void testRolling() { + final ConfigurationBuilder 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); + } + + @Test + void testBuilderWithScripts() { + final String script = + "if (logEvent.getLoggerName().equals(\"NoLocation\")) {\n" + " return \"NoLocation\";\n" + + " } else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf(\"FLOW\")) {\n" + + " return \"Flow\";\n" + + " } else {\n" + + " return null;\n" + + " }"; + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.ERROR); + builder.setConfigurationName("BuilderTest"); + builder.add(builder.newScriptFile("filter.groovy", "target/test-classes/scripts/filter.groovy") + .addIsWatched(true)); + final AppenderComponentBuilder appenderBuilder = + builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout") + .addComponent(builder.newComponent("ScriptPatternSelector") + .addAttribute("defaultPattern", "[%-5level] %c{1.} %C{1.}.%M.%L %msg%n") + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "NoLocation") + .addAttribute("pattern", "[%-5level] %c{1.} %msg%n")) + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "FLOW") + .addAttribute("pattern", "[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n")) + .addComponent(builder.newComponent("selectorScript", "Script", script) + .addAttribute("language", "beanshell")))); + appenderBuilder.add(builder.newFilter("ScriptFilter", Filter.Result.DENY, Filter.Result.NEUTRAL) + .addComponent(builder.newComponent("ScriptRef").addAttribute("ref", "filter.groovy"))); + 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))); + assertNotNull(config.getScriptManager().getScript("filter.groovy"), "Filter script not found"); + assertNotNull(config.getScriptManager().getScript("selectorScript"), "pattern selector script not found"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator2Test.java new file mode 100644 index 00000000000..55421ce8dfe --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator2Test.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.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +@Tag("functional") +class Configurator2Test { + + @Test + void testInitializeFromAbsoluteFilePath() { + final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath(); + testInitializeFromFilePath(path); + } + + @Test + void testInitializeFromRelativeFilePath() { + final String path = new File("src/test/resources/log4j-list.xml").toString(); + testInitializeFromFilePath(path); + } + + @Test + 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")); + final URI uri = loggerContext.getConfigLocation(); + assertNotNull(uri, "No configuration location returned"); + Configurator.reconfigure(); + assertEquals(uri, loggerContext.getConfigLocation(), "Unexpected configuration location returned"); + } + } + + @Test + 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")); + final 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 + 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/ConfiguratorErrorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorErrorTest.java new file mode 100644 index 00000000000..f00d31b4741 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorErrorTest.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 static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; +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; + +@ResourceLock("log4j2.LoggerContextFactory") +class ConfiguratorErrorTest { + + @RegisterExtension + static final LoggerContextFactoryExtension EXTENSION = + new LoggerContextFactoryExtension(SimpleLoggerContextFactory.INSTANCE); + + @Test + void testErrorNoClassLoader() { + try (final LoggerContext ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-config.xml")) { + assertNull(ctx, "No LoggerContext should have been returned"); + } + } + + @Test + void testErrorNullClassLoader() { + 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/ConfiguratorSetLevelTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorSetLevelTest.java new file mode 100644 index 00000000000..d5739c021d2 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +@Tag("functional") +@LoggerContextSource("log4j-set-level.xml") +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 + void testSetLevel() { + final Logger logger = loggerContext.getLogger(ConfiguratorSetLevelTest.class); + Configurator.setLevel(logger, Level.DEBUG); + final 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/CustomConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java new file mode 100644 index 00000000000..ac92595deff --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.Serializable; +import java.nio.file.Path; +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.SetTestProperty; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +class CustomConfigurationTest { + + @TempLoggingDir + private static Path loggingPath; + + @Test + @SetTestProperty(key = "log4j.level", value = "INFO") + @SetTestProperty(key = "log.level", value = "INFO") + @LoggerContextSource + void testConfig(final LoggerContext ctx) { + final Path logFile = loggingPath.resolve("test.log"); + // don't bother using "error" since that's the default; try another level + final Configuration config = ctx.getConfiguration(); + assertThat(config).isInstanceOf(XmlConfiguration.class); + for (final StatusListener listener : StatusLogger.getLogger().getListeners()) { + if (listener instanceof StatusConsoleListener) { + assertSame(Level.INFO, listener.getStatusLevel()); + break; + } + } + final Layout layout = PatternLayout.newBuilder() + .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .setConfiguration(config) + .build(); + final FileAppender appender = FileAppender.newBuilder() + .setBufferedIo(false) + .setIgnoreExceptions(false) + .setName("File") + .setLayout(layout) + .setAppend(false) + .setFileName(logFile.toString()) + .build(); + appender.start(); + config.addAppender(appender); + final AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); + final AppenderRef[] refs = new AppenderRef[] {ref}; + + final LoggerConfig loggerConfig = LoggerConfig.newBuilder() + .setConfig(config) + .setAdditivity(false) + .setIncludeLocation("true") + .setLevel(Level.INFO) + .setLoggerName("org.apache.logging.log4j") + .setRefs(refs) + .build(); + 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"); + assertThat(logFile).exists().isNotEmptyFile(); + } +} 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..3e7a7e137f4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/FileOutputTest.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.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.TempLoggingDir; +import org.junit.jupiter.api.Test; + +/** + * Tests the possibility to redirect status logger output to a file. + */ +class FileOutputTest { + + @TempLoggingDir + private static Path loggingPath; + + @Test + @LoggerContextSource + void testConfig() { + final Path logFile = loggingPath.resolve("status.log"); + assertThat(logFile).exists().isNotEmptyFile(); + // Closes the current listeners + StatusLogger.getLogger().reset(); + } +} 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..8962917a21e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.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.config; + +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.net.Advertiser; + +@Plugin(name = "Memory", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false) +public class InMemoryAdvertiser implements Advertiser { + private static Map> properties = new HashMap<>(); + + public static Map> getAdvertisedEntries() { + return new HashMap<>(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..65021c7d0ab --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertDoesNotThrow; + +import java.io.Serializable; +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.Layout; +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.Tag; +import org.junit.jupiter.api.Test; + +@Tag("yaml") +@LoggerContextSource("log4j2-2134.yaml") +class JiraLog4j2_2134Test { + + @Test + void testRefresh() { + final Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + final PatternLayout layout = PatternLayout.newBuilder() + // @formatter:off + .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .setConfiguration(config) + .build(); + final Layout layout1 = layout; + // @formatter:on + final Appender appender = FileAppender.newBuilder() + .setFileName("target/test.log") + .setLayout(layout1) + .setConfiguration(config) + .setBufferSize(4000) + .setName("File") + .build(); + // 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, "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 + void testRefreshMinimalCodeStart() { + final 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 + void testRefreshMinimalCodeStopStart() { + final Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + ctx.stop(); + ctx.start(); + + assertDoesNotThrow(() -> log.error("Info message")); + } + + @Test + void testRefreshMinimalCodeStopStartConfig() { + final 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 + void testRefreshDeprecatedApis() { + final Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + final PatternLayout layout = PatternLayout.createLayout( + PatternLayout.SIMPLE_CONVERSION_PATTERN, null, config, null, null, false, false, null, null); + final Appender appender = FileAppender.createAppender( + "target/test.log", + "false", + "false", + "File", + "true", + "false", + "false", + "4000", + layout, + null, + "false", + null, + config); + 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, "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/Log4j_3431_Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Log4j_3431_Test.java new file mode 100644 index 00000000000..a78f1e0f4a0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Log4j_3431_Test.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests the change for Log4j issue #3431. + *

+ * The configuration name should not be set to a default if a name already exists. + *

+ * + * @see + */ +@SuppressWarnings("NewClassNamingConvention") +class Log4j_3431_Test { + + /** + * Tests that the name of a configurations with no defined loggers is not reset when + * the configuration is started. + */ + @Test + void testConfigurationDefaults_WithName() { + + try (final LoggerContext ctx = new LoggerContext("Log4j_3431_Test")) { + + final String name = "Log4j_3431_Configuration"; + + Configuration config = ConfigurationBuilderFactory.newConfigurationBuilder() + .setConfigurationName(name) + .setConfigurationSource(ConfigurationSource.NULL_SOURCE) + .build(false); + + // a configuration with no defined loggers should trigger AbstractConfiguration 'setToDefault()' + // from 'doConfigure()' + + ctx.start(config); + + assertEquals(name, config.getName(), "The name of the configuration should be '" + name + "'"); + } + } + + /** + * Tests that the name of a configurations with no defined loggers is set to a default when + * the configuration is started. + */ + @Test + void testConfigurationDefaults_WithNoName() { + + try (final LoggerContext ctx = new LoggerContext("Log4j_3431_Test")) { + + final String name = "Log4j_3431_Configuration"; + + Configuration config = ConfigurationBuilderFactory.newConfigurationBuilder() + .setConfigurationSource(ConfigurationSource.NULL_SOURCE) + .build(false); + + // a configuration with no defined loggers should trigger AbstractConfiguration 'setToDefault()' + // from 'doConfigure()' + + ctx.start(config); + + final String expectedPrefix = DefaultConfiguration.DEFAULT_NAME + "@"; + Assertions.assertThatCharSequence(config.getName()) + .withFailMessage("The name of the configuration should start with '" + expectedPrefix + "'.") + .isNotBlank() + .startsWith(expectedPrefix); + } + } +} 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..b25ef14a48b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertNotSame; +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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +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.properties.PropertiesConfiguration; +import org.apache.logging.log4j.core.impl.Log4jLogEvent.Builder; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +/** + * Tests for LoggerConfig. + */ +class LoggerConfigTest { + + private static final String FQCN = LoggerConfigTest.class.getName(); + + private static LoggerConfig createForProperties(final Property[] properties) { + return LoggerConfig.newBuilder() + .setConfig(new NullConfiguration()) + .setAdditivity(true) + .setLevel(Level.INFO) + .setLoggerName("name") + .setIncludeLocation("false") + .setProperties(properties) + .build(); + } + + @Test + 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 AtomicReference actualList = new AtomicReference<>(); + loggerConfig.setLogEventFactory((loggerName, marker, fqcn, level, data, properties, t) -> { + actualList.set(properties); + return new Builder().setTimeMillis(System.currentTimeMillis()).build(); + }); + loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null); + assertSame(list, actualList.get(), "propertiesList passed in as is if no substitutions required"); + } + + @Test + 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 AtomicReference actualListHolder = new AtomicReference<>(); + loggerConfig.setLogEventFactory((loggerName, marker, fqcn, level, data, properties, t) -> { + actualListHolder.set(properties); + return new Builder().setTimeMillis(System.currentTimeMillis()).build(); + }); + loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null); + assertNotSame(list, actualListHolder.get(), "propertiesList with substitutions"); + + @SuppressWarnings("unchecked") + final List actualList = (List) actualListHolder.get(); + + 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 + void testLevel() { + final Configuration configuration = new DefaultConfiguration(); + final LoggerConfig config1 = LoggerConfig.newBuilder() + .setLoggerName("org.apache.logging.log4j.test") + .setLevel(Level.ERROR) + .setAdditivity(false) + .setConfig(configuration) + .build(); + final LoggerConfig config2 = LoggerConfig.newBuilder() + .setLoggerName("org.apache.logging.log4j") + .setAdditivity(false) + .setConfig(configuration) + .build(); + config1.setParent(config2); + assertEquals(Level.ERROR, config1.getLevel(), "Unexpected Level"); + assertEquals(Level.ERROR, config1.getExplicitLevel(), "Unexpected explicit level"); + assertEquals(Level.ERROR, config2.getLevel(), "Unexpected Level"); + assertNull(config2.getExplicitLevel(), "Unexpected explicit level"); + } + + @Test + void testSingleFilterInvocation() { + final Configuration configuration = new NullConfiguration(); + final Filter filter = mock(Filter.class); + final LoggerConfig config = LoggerConfig.newBuilder() + .setLoggerName(FQCN) + .setConfig(configuration) + .setLevel(Level.INFO) + .setFilter(filter) + .build(); + final Appender appender = mock(Appender.class); + when(appender.isStarted()).thenReturn(true); + when(appender.getName()).thenReturn("test"); + config.addAppender(appender, null, null); + + config.log(FQCN, FQCN, null, Level.INFO, new SimpleMessage(), null); + verify(appender, times(1)).append(any()); + verify(filter, times(1)).filter(any()); + } + + @Test + void testLevelAndRefsWithoutAppenderRef() { + final Configuration configuration = mock(PropertiesConfiguration.class); + final LoggerConfig.Builder builder = LoggerConfig.newBuilder() + .setLoggerName(FQCN) + .setConfig(configuration) + .setLevelAndRefs(Level.INFO.name()); + + final LoggerConfig loggerConfig = builder.build(); + + assertNotNull(loggerConfig.getAppenderRefs()); + assertTrue(loggerConfig.getAppenderRefs().isEmpty()); + } +} 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..382587b4a8b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.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.config; + +import static org.assertj.core.api.Assertions.assertThat; + +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.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Tests LoggersPlugin. + */ +@SetSystemProperty(key = "log4j2.status.entries", value = "10") +class LoggersPluginTest { + + @Test + @LoggerContextSource("multipleRootLoggersTest.xml") + void testEmptyAttribute() { + final Logger logger = LogManager.getLogger(); + logger.info("Test"); + final StatusData data = StatusLogger.getLogger().getStatusData().get(0); + + assertThat(data.getLevel()).isEqualTo(Level.ERROR); + assertThat(data.getMessage().getFormattedMessage()).contains("multiple root loggers"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingLanguageTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingLanguageTest.java new file mode 100644 index 00000000000..721c0e3090d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingLanguageTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +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.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.util.Constants; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "beanshell, Javascript") +@Tag("functional") +class MissingLanguageTest { + + private LoggerContext ctx = null; + + @AfterEach + void cleanup() { + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + if (ctx != null) { + Configurator.shutdown(ctx); + ctx = null; + } + } + + @org.junit.jupiter.api.Test + void testBuilderWithScripts() { + final String script = + "if (logEvent.getLoggerName().equals(\"NoLocation\")) {\n" + " return \"NoLocation\";\n" + + " } else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf(\"FLOW\")) {\n" + + " return \"Flow\";\n" + + " } else {\n" + + " return null;\n" + + " }"; + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.ERROR); + builder.setConfigurationName("BuilderTest"); + builder.add(builder.newScriptFile("filter.groovy", "target/test-classes/scripts/filter.groovy") + .addIsWatched(true)); + final AppenderComponentBuilder appenderBuilder = + builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout") + .addComponent(builder.newComponent("ScriptPatternSelector") + .addAttribute("defaultPattern", "[%-5level] %c{1.} %C{1.}.%M.%L %msg%n") + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "NoLocation") + .addAttribute("pattern", "[%-5level] %c{1.} %msg%n")) + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "FLOW") + .addAttribute("pattern", "[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n")) + .addComponent(builder.newComponent("selectorScript", "Script", script) + .addAttribute("language", "beanshell")))); + appenderBuilder.add(builder.newFilter("ScriptFilter", Filter.Result.DENY, Filter.Result.NEUTRAL) + .addComponent(builder.newComponent("ScriptRef").addAttribute("ref", "filter.groovy"))); + 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.getScriptManager(), "No ScriptManager"); + assertNull(config.getScriptManager().getScript("filter.groovy"), "Script should not be present"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..13bd9706aa8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.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.config; + +import static org.apache.logging.log4j.core.test.hamcrest.MapMatchers.hasSize; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +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.Map; +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.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("missingRootLogger.xml") +class MissingRootLoggerTest { + + @Test + void testMissingRootLogger(final LoggerContext ctx) { + final Logger logger = ctx.getLogger("sample.Logger1"); + 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, "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(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, "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")); + + final LoggerConfig sample = loggerMap.get("sample"); + final Map sampleAppenders = sample.getAppenders(); + assertThat("The sample logger should only have one appender", sampleAppenders, hasSize(1)); + // sample only has List appender, not Console! + assertThat("The sample appender should be a ListAppender", sampleAppenders, hasKey("List")); + assertThat(config, is(instanceOf(AbstractConfiguration.class))); + final AbstractConfiguration baseConfig = (AbstractConfiguration) config; + final LoggerConfig root = baseConfig.getRootLogger(); + final Map rootAppenders = root.getAppenders(); + assertThat("The root logger should only have one appender", rootAppenders, hasSize(1)); + // root only has Console appender! + assertThat("The root appender should be a ConsoleAppender", rootAppenders, hasKey("DefaultConsole-2")); + assertEquals(Level.ERROR, root.getLevel()); + } +} 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..18eb5406f6a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MockReliabilityStrategy.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.config; + +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +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; + +/** + * 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 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..8ddac378400 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MultipleTriggeringPolicyTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +/** + * 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..46211e4b072 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NestedLoggerConfigTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests for LoggerConfig hierarchies. + */ +class NestedLoggerConfigTest { + + public static List data() { + return ImmutableList.of("logger-config/LoggerConfig/", "logger-config/AsyncLoggerConfig/"); + } + + @MethodSource("data") + @ParameterizedTest(name = "{0}") + void testInheritParentDefaultLevel(final String prefix) throws IOException { + final Configuration configuration = loadConfiguration(prefix + "default-level.xml"); + try { + assertEquals(Level.ERROR, configuration.getLoggerConfig("com.foo").getLevel()); + } finally { + configuration.stop(); + } + } + + @MethodSource("data") + @ParameterizedTest(name = "{0}") + void testInheritParentLevel(final String prefix) throws IOException { + final Configuration configuration = loadConfiguration(prefix + "inherit-level.xml"); + try { + assertEquals(Level.TRACE, configuration.getLoggerConfig("com.foo").getLevel()); + } finally { + configuration.stop(); + } + } + + private Configuration loadConfiguration(final String resourcePath) throws IOException { + try (final InputStream in = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + final 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/NoLanguagesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NoLanguagesTest.java new file mode 100644 index 00000000000..26b69be6d56 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NoLanguagesTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +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.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class NoLanguagesTest { + + private LoggerContext ctx = null; + + @AfterEach + void cleanup() { + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + if (ctx != null) { + Configurator.shutdown(ctx); + ctx = null; + } + } + + @Test + void testBuilderWithScripts() { + final String script = + "if (logEvent.getLoggerName().equals(\"NoLocation\")) {\n" + " return \"NoLocation\";\n" + + " } else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf(\"FLOW\")) {\n" + + " return \"Flow\";\n" + + " } else {\n" + + " return null;\n" + + " }"; + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.ERROR); + builder.setConfigurationName("BuilderTest"); + builder.add(builder.newScriptFile("filter.groovy", "target/test-classes/scripts/filter.groovy") + .addIsWatched(true)); + final AppenderComponentBuilder appenderBuilder = + builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout") + .addComponent(builder.newComponent("ScriptPatternSelector") + .addAttribute("defaultPattern", "[%-5level] %c{1.} %C{1.}.%M.%L %msg%n") + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "NoLocation") + .addAttribute("pattern", "[%-5level] %c{1.} %msg%n")) + .addComponent(builder.newComponent("PatternMatch") + .addAttribute("key", "FLOW") + .addAttribute("pattern", "[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n")) + .addComponent(builder.newComponent("selectorScript", "Script", script) + .addAttribute("language", "beanshell")))); + appenderBuilder.add(builder.newFilter("ScriptFilter", Filter.Result.DENY, Filter.Result.NEUTRAL) + .addComponent(builder.newComponent("ScriptRef").addAttribute("ref", "filter.groovy"))); + 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(); + assertNull(config.getScriptManager()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertiesPluginTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertiesPluginTest.java new file mode 100644 index 00000000000..bbaa4894abe --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertiesPluginTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class PropertiesPluginTest { + + @Test + void testUnescape() { + assertEquals("${foo}", PropertiesPlugin.unescape("$${foo}")); + } + + @Test + void testUnescapeNotEscapedWithDefault() { + final String value = "${foo:-bar}"; + assertEquals(value, PropertiesPlugin.unescape(value)); + } +} 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..46539b41cd1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.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.config; + +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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +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; + +/** + * Test for LOG4J2-1313 + * not working + */ +@LoggerContextSource("configPropertyTest.xml") +class PropertyTest { + + @Test + void testEmptyAttribute(@Named("List") final ListAppender app) { + 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 + void testPropertyValues() { + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final StrSubstitutor sub = ctx.getConfiguration().getStrSubstitutor(); + // + // + // + // elementValue + // + // + // elementValue3 + assertEquals("", sub.replace("${emptyElementKey}")); + assertEquals("", sub.replace("${emptyAttributeKey}")); + assertEquals("", sub.replace("${emptyAttributeKey2}")); + assertEquals("elementValue", sub.replace("${elementKey}")); + assertEquals("attributeValue", sub.replace("${attributeKey}")); + assertEquals("attributeValue2", sub.replace("${attributeWithEmptyElementKey}")); + assertEquals("elementValue3", sub.replace("${bothElementAndAttributeKey}")); + } + + @Test + void testLoggerPropertyValues() { + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final List rootLoggerProperties = + ctx.getConfiguration().getLoggerConfig(LoggerConfig.ROOT).getPropertyList(); + // + // + // + // elementValue + // + // + // elementValue3 + assertEquals(9, rootLoggerProperties.size()); + verifyProperty(rootLoggerProperties.get(0), "emptyElementKey", "", ""); + verifyProperty(rootLoggerProperties.get(1), "emptyAttributeKey", "", ""); + verifyProperty(rootLoggerProperties.get(2), "emptyAttributeKey2", "", ""); + verifyProperty(rootLoggerProperties.get(3), "elementKey", "elementValue", "elementValue"); + verifyProperty(rootLoggerProperties.get(4), "attributeKey", "attributeValue", "attributeValue"); + verifyProperty( + rootLoggerProperties.get(5), "attributeWithEmptyElementKey", "attributeValue2", "attributeValue2"); + verifyProperty(rootLoggerProperties.get(6), "bothElementAndAttributeKey", "elementValue3", "elementValue3"); + verifyProperty(rootLoggerProperties.get(7), "attributeWithLookup", "${lower:ATTR}", "attr"); + verifyProperty(rootLoggerProperties.get(8), "elementWithLookup", "${lower:ELEMENT}", "element"); + } + + private static void verifyProperty( + final Property property, + final String expectedName, + final String expectedRawValue, + final String expectedValue) { + assertEquals(expectedName, property.getName()); + assertEquals(expectedRawValue, property.getRawValue()); + assertEquals(expectedValue, property.getValue()); + } + + @Test + void testNullValueIsConvertedToEmptyString() { // LOG4J2-1313 support + assertEquals("", Property.createProperty("name", null).getValue()); + } + + @Test + 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..2a595b533fe --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; +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.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.appender.AbstractAppender; +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.test.junit.LoggerContextSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; + +/** + * Performs reconfiguration whilst logging. + * + * @see LOG4J2-620 + * @see TestAppender + */ +@LoggerContextSource("reconfiguration-deadlock.xml") +class ReconfigurationDeadlockTest { + + private static final int WORKER_COUNT = 100; + + private ExecutorService executor; + + @BeforeEach + void startExecutor() { + executor = Executors.newFixedThreadPool(WORKER_COUNT); + } + + @AfterEach + void stopExecutor() throws InterruptedException { + executor.shutdownNow(); + final boolean terminated = executor.awaitTermination(30, TimeUnit.SECONDS); + assertTrue(terminated, "couldn't terminate the executor"); + } + + @RepeatedTest(100) + 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); + final 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 { + final Object workerResult = workerFuture.get(30, TimeUnit.SECONDS); + 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()); + 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. + */ + @Plugin( + name = "ReconfigurationDeadlockTestAppender", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) + 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/ReconfigurationFailureTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationFailureTest.java new file mode 100644 index 00000000000..14df356b901 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationFailureTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.net.URI; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Performs reconfiguration against bad configurations. + * + */ +// Remove this when LOG4J2-2240 is addressed. +@Disabled +class ReconfigurationFailureTest { + + LoggerContext loggerContext; + + @BeforeEach + void setup() { + loggerContext = (LoggerContext) LogManager.getContext(); + } + + @AfterEach + void stopExecutor() { + loggerContext.stop(); + } + + @Test + void setNonExistant() { + + final URI original = loggerContext.getConfigLocation(); + final URI nonExistant = new File("target/file.does.not.exist.xml").toURI(); + loggerContext.setConfigLocation(nonExistant); + assertEquals(original, loggerContext.getConfigLocation(), "URI after failure is not the original"); + } + + @Test + void setInvalidXML() { + + final URI original = loggerContext.getConfigLocation(); + final URI nonExistant = new File("target/InvalidXML.xml").toURI(); + loggerContext.setConfigLocation(nonExistant); + assertEquals(original, loggerContext.getConfigLocation(), "URI after failure is not the original"); + } + + @Test + void setInvalidConfig() { + + final URI original = loggerContext.getConfigLocation(); + final URI nonExistant = new File("target/InvalidConfig.xml").toURI(); + loggerContext.setConfigLocation(nonExistant); + assertEquals(original, loggerContext.getConfigLocation(), "URI after failure is not the original"); + } +} 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..906e6c43acf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReliabilityStrategyTest.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.config; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class ReliabilityStrategyTest { + + @BeforeAll + static void setUp() { + System.setProperty("log4j2.reliabilityStrategy", MockReliabilityStrategy.class.getName()); + } + + @AfterAll + static void tearDown() { + System.clearProperty("log4j2.reliabilityStrategy"); + } + + @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/ScriptsPluginTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ScriptsPluginTest.java new file mode 100644 index 00000000000..0ea1acd6ef7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ScriptsPluginTest.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 static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.logging.log4j.core.script.AbstractScript; +import org.junit.jupiter.api.Test; + +class ScriptsPluginTest { + + @Test + void testCreateScriptsNullInput() { + assertThrows(NullPointerException.class, () -> ScriptsPlugin.createScripts(null)); + } + + @Test + void testCreateScriptsEmptyInput() { + AbstractScript[] emptyArray = new AbstractScript[0]; + assertSame(emptyArray, ScriptsPlugin.createScripts(emptyArray), "Should return empty array"); + } + + @Test + void testCreateScriptsAllExplicitNames() { + AbstractScript script1 = new MockScript("script1", "JavaScript", "text"); + AbstractScript script2 = new MockScript("script2", "Groovy", "text"); + AbstractScript[] input = {script1, script2}; + AbstractScript[] result = ScriptsPlugin.createScripts(input); + assertEquals(2, result.length, "Should return 2 scripts"); + assertArrayEquals(input, result, "Should contain all valid scripts"); + } + + @Test + void testCreateScriptsImplicitName() { + AbstractScript script = new MockScript(null, "JavaScript", "text"); + AbstractScript[] input = {script}; + assertThrows(ConfigurationException.class, () -> ScriptsPlugin.createScripts(input)); + } + + @Test + void testCreateScriptsBlankName() { + AbstractScript script = new MockScript(" ", "JavaScript", "text"); + AbstractScript[] input = {script}; + assertThrows(ConfigurationException.class, () -> ScriptsPlugin.createScripts(input)); + } + + @Test + void testCreateScriptsMixedExplicitAndImplicitNames() { + AbstractScript explicitScript = new MockScript("script", "Python", "text"); + AbstractScript implicitScript = new MockScript(null, "JavaScript", "text"); + AbstractScript[] input = {explicitScript, implicitScript}; + assertThrows(ConfigurationException.class, () -> ScriptsPlugin.createScripts(input)); + } + + private class MockScript extends AbstractScript { + + public MockScript(String name, String language, String scriptText) { + super(name, language, scriptText); + } + } +} 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..ad898fb95a4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/BasicArbiterTest.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.config.arbiters; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +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; + +/** + * Tests basic condition processing. + */ +class BasicArbiterTest { + + static final String CONFIG = "log4j2-arbiters.xml"; + static LoggerContext loggerContext = null; + + @AfterEach + void after() { + loggerContext.stop(); + loggerContext = null; + System.clearProperty("env"); + } + + @Test + void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + } + + @Test + void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ConsoleAppender.class, app); + } + + @Test + void classArbiterTest() { + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("ShouldExist"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + app = loggerContext.getConfiguration().getAppender("ShouldNotExist"); + assertNull(app); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/EnvironmentArbiterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/EnvironmentArbiterTest.java new file mode 100644 index 00000000000..ecb5c24d06b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/EnvironmentArbiterTest.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.config.arbiters; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; + +/** + * Tests system property condition processing. + */ +class EnvironmentArbiterTest { + + private static final String CONFIG = "log4j2-environmentArbiters.xml"; + + @Test + @SetEnvironmentVariable(key = "ENV", value = "prod") + @LoggerContextSource(CONFIG) + void prodTest(final LoggerContext loggerContext) { + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + } + + @Test + @SetEnvironmentVariable(key = "ENV", value = "dev") + @LoggerContextSource(CONFIG) + void devTest(final LoggerContext loggerContext) { + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ConsoleAppender.class, app); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiterTest.java new file mode 100644 index 00000000000..50899118c90 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiterTest.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.config.arbiters; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Tests basic condition processing. + */ +class ScriptArbiterTest { + + static final String CONFIG = "log4j2-scriptArbiters.xml"; + static LoggerContext loggerContext = null; + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.SCRIPT_LANGUAGES, "Groovy, Javascript"); + } + + @AfterEach + void after() { + loggerContext.stop(); + loggerContext = null; + } + + @Test + void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + } + + @Test + void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ConsoleAppender.class, 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..07f1520afd7 --- /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 static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +/** + * Tests basic condition processing. + */ +class SelectArbiterTest { + + static final String CONFIG = "log4j2-selectArbiters.xml"; + static LoggerContext loggerContext = null; + + @AfterEach + void after() { + loggerContext.stop(); + loggerContext = null; + } + + @Test + void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + } + + @Test + void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ConsoleAppender.class, app); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiterTest.java new file mode 100644 index 00000000000..692d9997442 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiterTest.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.config.arbiters; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +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; + +/** + * Tests system property condition processing. + */ +class SystemPropertyArbiterTest { + + static final String CONFIG = "log4j2-systemPropertyArbiters.xml"; + static LoggerContext loggerContext = null; + + @AfterEach + void after() { + loggerContext.stop(); + loggerContext = null; + System.clearProperty("env"); + } + + @Test + void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + } + + @Test + void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + final Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertInstanceOf(ConsoleAppender.class, app); + } + + @Test + void classArbiterTest() { + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("ShouldExist"); + assertNotNull(app); + assertInstanceOf(ListAppender.class, app); + app = loggerContext.getConfiguration().getAppender("ShouldNotExist"); + assertNull(app); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationAssemblerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationAssemblerTest.java new file mode 100644 index 00000000000..0d6b74f33e6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationAssemblerTest.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.config.builder; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +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.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.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.CustomLevelConfig; +import org.apache.logging.log4j.core.config.LoggerConfig; +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.filter.ThresholdFilter; +import org.apache.logging.log4j.core.layout.GelfLayout; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.jupiter.api.Test; + +class ConfigurationAssemblerTest { + + @Test + void testBuildConfiguration() { + try { + System.setProperty( + Constants.LOG4J_CONTEXT_SELECTOR, "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"); + final ConfigurationBuilder builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + CustomConfigurationFactory.addTestFixtures("config name", builder); + final Configuration configuration = builder.build(); + try (final LoggerContext ctx = Configurator.initialize(configuration)) { + validate(configuration); + } + } finally { + System.getProperties().remove(Constants.LOG4J_CONTEXT_SELECTOR); + } + } + + @Test + void testCustomConfigurationFactory() { + try { + System.setProperty( + ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY, + "org.apache.logging.log4j.core.config.builder.CustomConfigurationFactory"); + System.setProperty( + Constants.LOG4J_CONTEXT_SELECTOR, "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"); + final Configuration config = ((LoggerContext) LogManager.getContext(false)).getConfiguration(); + validate(config); + } finally { + System.getProperties().remove(Constants.LOG4J_CONTEXT_SELECTOR); + System.getProperties().remove(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY); + } + } + + private void validate(final Configuration config) { + assertNotNull(config); + assertNotNull(config.getName()); + assertFalse(config.getName().isEmpty()); + assertNotNull(config, "No configuration created"); + assertEquals(LifeCycle.State.STARTED, config.getState(), "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(2, appenders.size(), "Incorrect number of Appenders: " + appenders.size()); + final KafkaAppender kafkaAppender = (KafkaAppender) appenders.get("Kafka"); + final GelfLayout gelfLayout = (GelfLayout) kafkaAppender.getLayout(); + final ConsoleAppender consoleAppender = (ConsoleAppender) appenders.get("Stdout"); + final PatternLayout patternLayout = (PatternLayout) consoleAppender.getLayout(); + final Map loggers = config.getLoggers(); + assertNotNull(loggers); + assertEquals(2, loggers.size(), "Incorrect number of LoggerConfigs: " + loggers.size()); + final LoggerConfig rootLoggerConfig = loggers.get(""); + assertEquals(Level.ERROR, rootLoggerConfig.getLevel()); + assertFalse(rootLoggerConfig.isIncludeLocation()); + final LoggerConfig loggerConfig = loggers.get("org.apache.logging.log4j"); + assertEquals(Level.DEBUG, loggerConfig.getLevel()); + assertTrue(loggerConfig.isIncludeLocation()); + final Filter filter = config.getFilter(); + assertNotNull(filter, "No Filter"); + assertThat(filter, instanceOf(ThresholdFilter.class)); + final List customLevels = config.getCustomLevels(); + assertNotNull(filter, "No CustomLevels"); + assertEquals(1, customLevels.size()); + final CustomLevelConfig customLevel = customLevels.get(0); + assertEquals("Panic", customLevel.getLevelName()); + assertEquals(17, customLevel.getIntLevel()); + final Logger logger = LogManager.getLogger(getClass()); + logger.info("Welcome to Log4j!"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationBuilderTest.java new file mode 100644 index 00000000000..f2dbdf0bc4f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/ConfigurationBuilderTest.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.config.builder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +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.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.junit.jupiter.api.Test; + +class ConfigurationBuilderTest { + + private static final String INDENT = " "; + private static final String EOL = System.lineSeparator(); + + private void addTestFixtures(final String name, final ConfigurationBuilder builder) { + builder.setConfigurationName(name); + builder.setStatusLevel(Level.ERROR); + builder.setShutdownTimeout(5000, TimeUnit.MILLISECONDS); + builder.add(builder.newScriptFile("target/test-classes/scripts/filter.groovy") + .addIsWatched(true)); + 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); + + final AppenderComponentBuilder appenderBuilder2 = + builder.newAppender("Kafka", "Kafka").addAttribute("topic", "my-topic"); + appenderBuilder2.addComponent(builder.newProperty("bootstrap.servers", "localhost:9092")); + appenderBuilder2.add(builder.newLayout("GelfLayout") + .addAttribute("host", "my-host") + .addComponent(builder.newKeyValuePair("extraField", "extraValue"))); + builder.add(appenderBuilder2); + + builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG, true) + .add(builder.newAppenderRef("Stdout")) + .addAttribute("additivity", false)); + builder.add(builder.newLogger("org.apache.logging.log4j.core").add(builder.newAppenderRef("Stdout"))); + builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); + + builder.addProperty("MyKey", "MyValue"); + builder.add(builder.newCustomLevel("Panic", 17)); + builder.setPackages("foo,bar"); + } + + private static final String expectedXml = "" /*+ EOL*/ + + "" + + EOL + INDENT + + "" + EOL + INDENT + + INDENT + "MyValue" + EOL + INDENT + + "" + EOL + INDENT + + "" + EOL + INDENT + + INDENT + + "" + + EOL + INDENT + + "" + EOL + INDENT + + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + "" + EOL + INDENT + + "" + EOL + INDENT + + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "localhost:9092" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + INDENT + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + "" + EOL + INDENT + + "" + EOL + INDENT + + INDENT + + "" + + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + INDENT + INDENT + "" + EOL + INDENT + + INDENT + "" + EOL + INDENT + + "" + EOL + "" + + EOL; + + @Test + void testXmlConstructing() { + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + addTestFixtures("config name", builder); + final String xmlConfiguration = builder.toXmlConfiguration(); + assertEquals(expectedXml, xmlConfiguration); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomConfigurationFactory.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomConfigurationFactory.java new file mode 100644 index 00000000000..737c2181e77 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomConfigurationFactory.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.config.builder; + +import java.net.URI; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +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.builder.api.AppenderComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; + +/** + * Normally this would be a plugin. However, we don't want it used for everything so it will be defined + * via a system property. + */ +// @Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY) +// @Order(50) +public class CustomConfigurationFactory extends ConfigurationFactory { + + static Configuration addTestFixtures(final String name, final ConfigurationBuilder builder) { + builder.setConfigurationName(name); + builder.setStatusLevel(Level.ERROR); + builder.add(builder.newScriptFile("target/test-classes/scripts/filter.groovy") + .addIsWatched(true)); + 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); + + final AppenderComponentBuilder appenderBuilder2 = + builder.newAppender("Kafka", "Kafka").addAttribute("topic", "my-topic"); + appenderBuilder2.addComponent(builder.newProperty("bootstrap.servers", "localhost:9092")); + appenderBuilder2.add(builder.newLayout("GelfLayout") + .addAttribute("host", "my-host") + .addComponent(builder.newKeyValuePair("extraField", "extraValue"))); + builder.add(appenderBuilder2); + + builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG, true) + .add(builder.newAppenderRef("Stdout")) + .addAttribute("additivity", false)); + builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); + + builder.add(builder.newCustomLevel("Panic", 17)); + + 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) { + final ConfigurationBuilder builder = newConfigurationBuilder(); + return addTestFixtures(name, builder); + } + + @Override + protected String[] getSupportedTypes() { + return new String[] {"*"}; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverterTest.java new file mode 100644 index 00000000000..1ccd7d032d9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverterTest.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.plugins.convert; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Date; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * + */ +class DateTypeConverterTest { + + public static Object[][] data() { + final long millis = System.currentTimeMillis(); + return new Object[][] { + {Date.class, millis, new Date(millis)}, + {java.sql.Date.class, millis, new java.sql.Date(millis)}, + {Time.class, millis, new Time(millis)}, + {Timestamp.class, millis, new Timestamp(millis)} + }; + } + + @MethodSource("data") + @ParameterizedTest + void testFromMillis(final Class dateClass, final long timestamp, final Object expected) { + assertEquals(expected, DateTypeConverter.fromMillis(timestamp, dateClass)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java new file mode 100644 index 00000000000..5af8220e11e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.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.convert; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class TypeConverterRegistryTest { + + @Test + void testFindNullConverter() { + assertThrows(NullPointerException.class, () -> TypeConverterRegistry.getInstance() + .findCompatibleConverter(null)); + } + + @Test + void testFindBooleanConverter() throws Exception { + final TypeConverter converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.class); + assertNotNull(converter); + assertTrue((Boolean) converter.convert("TRUE")); + } + + @Test + void testFindPrimitiveBooleanConverter() throws Exception { + final TypeConverter converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.TYPE); + assertNotNull(converter); + assertTrue((Boolean) converter.convert("tRUe")); + } + + @SuppressWarnings("unchecked") + @Test + void testFindCharSequenceConverterUsingStringConverter() throws Exception { + final TypeConverter converter = (TypeConverter) + TypeConverterRegistry.getInstance().findCompatibleConverter(CharSequence.class); + assertNotNull(converter); + assertThat(converter, instanceOf(TypeConverters.StringConverter.class)); + final CharSequence expected = "This is a test sequence of characters"; + final CharSequence actual = converter.convert(expected.toString()); + assertEquals(expected, actual); + } + + @SuppressWarnings("unchecked") + @Test + void testFindNumberConverter() { + final TypeConverter numberTypeConverter = + (TypeConverter) TypeConverterRegistry.getInstance().findCompatibleConverter(Number.class); + assertNotNull(numberTypeConverter); + // TODO: is there a specific converter this should return? + } + + public enum Foo { + I, + PITY, + THE + } + + @SuppressWarnings("unchecked") + @Test + void testFindEnumConverter() throws Exception { + final TypeConverter fooTypeConverter = + (TypeConverter) TypeConverterRegistry.getInstance().findCompatibleConverter(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/config/plugins/convert/TypeConvertersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java new file mode 100644 index 00000000000..1fc961db42d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests {@link TypeConverters}. + */ +public class TypeConvertersTest { + + @SuppressWarnings("boxing") + public static Collection data() throws Exception { + final byte[] byteArray = { + (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 + }; + return Arrays.asList( + // Array format: value, expected, default, type + new Object[][] { + // boolean + {"true", true, null, Boolean.class}, + {"false", false, null, Boolean.class}, + {"True", true, null, Boolean.class}, + {"TRUE", true, null, Boolean.class}, + {"blah", false, null, Boolean.class + }, // TODO: is this acceptable? it's how Boolean.parseBoolean works + {null, null, null, Boolean.class}, + {null, true, "true", Boolean.class}, + {"no", false, null, Boolean.class}, // TODO: see above + {"true", true, "false", boolean.class}, + {"FALSE", false, "true", boolean.class}, + {null, false, "false", boolean.class}, + {"invalid", false, "false", boolean.class}, + // byte + {"42", (byte) 42, null, Byte.class}, + {"53", (byte) 53, null, Byte.class}, + // char + {"A", 'A', null, char.class}, + {"b", 'b', null, char.class}, + {"b0", null, null, char.class}, + // integer + {"42", 42, null, Integer.class}, + {"53", 53, null, Integer.class}, + {"-16", -16, null, Integer.class}, + {"0", 0, null, Integer.class}, + {"n", null, null, Integer.class}, + {"n", 5, "5", Integer.class}, + {"4.2", null, null, Integer.class}, + {"4.2", 0, "0", Integer.class}, + {null, null, null, Integer.class}, + {"75", 75, "0", int.class}, + {"-30", -30, "0", int.class}, + {"0", 0, "10", int.class}, + {null, 10, "10", int.class}, + // longs + {"55", 55L, null, Long.class}, + {"1234567890123456789", 1234567890123456789L, null, Long.class}, + {"123123123L", null, null, Long.class}, + {"123123123123", 123123123123L, null, Long.class}, + {"-987654321", -987654321L, null, Long.class}, + {"-45l", null, null, Long.class}, + {"0", 0L, null, Long.class}, + {"asdf", null, null, Long.class}, + {"3.14", null, null, Long.class}, + {"3.14", 0L, "0", Long.class}, + {"*3", 1000L, "1000", Long.class}, + {null, null, null, Long.class}, + {"3000", 3000L, "0", long.class}, + {"-543210", -543210L, "0", long.class}, + {"22.7", -53L, "-53", long.class}, + // short + {"42", (short) 42, null, short.class}, + {"53", (short) 53, null, short.class}, + {"-16", (short) -16, null, Short.class}, + // Log4j + // levels + {"ERROR", Level.ERROR, null, Level.class}, + {"WARN", Level.WARN, null, Level.class}, + {"FOO", null, null, Level.class}, + {"FOO", Level.DEBUG, "DEBUG", Level.class}, + {"OFF", Level.OFF, null, Level.class}, + {null, null, null, Level.class}, + {null, Level.INFO, "INFO", Level.class}, + // results + {"ACCEPT", Filter.Result.ACCEPT, null, Filter.Result.class}, + {"NEUTRAL", Filter.Result.NEUTRAL, null, Filter.Result.class}, + {"DENY", Filter.Result.DENY, null, Filter.Result.class}, + {"NONE", null, null, Filter.Result.class}, + {"NONE", Filter.Result.NEUTRAL, "NEUTRAL", Filter.Result.class}, + {null, null, null, Filter.Result.class}, + {null, Filter.Result.ACCEPT, "ACCEPT", Filter.Result.class}, + // syslog facilities + {"KERN", Facility.KERN, "USER", Facility.class}, + {"mail", Facility.MAIL, "KERN", Facility.class}, + {"Cron", Facility.CRON, null, Facility.class}, + {"not a real facility", Facility.AUTH, "auth", Facility.class}, + {null, null, null, Facility.class}, + // GELF compression types + {"GZIP", GelfLayout.CompressionType.GZIP, "GZIP", GelfLayout.CompressionType.class}, + {"ZLIB", GelfLayout.CompressionType.ZLIB, "GZIP", GelfLayout.CompressionType.class}, + {"OFF", GelfLayout.CompressionType.OFF, "GZIP", GelfLayout.CompressionType.class}, + // arrays + {"123", "123".toCharArray(), null, char[].class}, + {"123", "123".getBytes(Charset.defaultCharset()), null, byte[].class}, + {"0xC773218C7EC8EE99", byteArray, null, byte[].class}, + {"0xc773218c7ec8ee99", byteArray, 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", 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}, + // JRE File + {"c:/temp", new File("c:/temp"), null, File.class}, + // JRE Class + {TypeConvertersTest.class.getName(), TypeConvertersTest.class, null, Class.class}, + {"boolean", boolean.class, null, Class.class}, + {"byte", byte.class, null, Class.class}, + {"char", char.class, null, Class.class}, + {"double", double.class, null, Class.class}, + {"float", float.class, null, Class.class}, + {"int", int.class, null, Class.class}, + {"long", long.class, null, Class.class}, + {"short", short.class, null, Class.class}, + {"void", void.class, null, Class.class}, + {"\t", Object.class, Object.class.getName(), Class.class}, + {"\n", null, null, Class.class}, + // JRE URL + {"http://locahost", new URL("http://locahost"), null, URL.class}, + {"\n", null, null, URL.class}, + // JRE URI + {"http://locahost", new URI("http://locahost"), null, URI.class}, + {"\n", null, null, URI.class}, + // JRE BigInteger + {"9223372036854775817000", new BigInteger("9223372036854775817000"), null, BigInteger.class}, + {"\n", null, null, BigInteger.class}, + // JRE BigInteger + { + "9223372036854775817000.99999", + new BigDecimal("9223372036854775817000.99999"), + null, + BigDecimal.class + }, + {"\n", null, null, BigDecimal.class}, + // JRE Security Provider + {Security.getProviders()[0].getName(), Security.getProviders()[0], null, Provider.class}, + {"\n", null, null, Provider.class}, + // Duration + {"P7DT10H", Duration.parse("P7DT10H"), null, Duration.class}, + // JRE InetAddress + {"127.0.0.1", InetAddress.getByName("127.0.0.1"), null, InetAddress.class}, + // JRE Path + {"/path/to/file", Paths.get("/path", "to", "file"), null, Path.class}, + // JRE UUID + { + "8fd389fb-9154-4096-b52e-435bde4a1835", + UUID.fromString("8fd389fb-9154-4096-b52e-435bde4a1835"), + null, + UUID.class + }, + }); + } + + @MethodSource("data") + @ParameterizedTest + void testConvert(final String value, final Object expected, final String defaultValue, final Class clazz) { + final Object actual = TypeConverters.convert(value, clazz, defaultValue); + final String assertionMessage = "\nGiven: " + value + "\nDefault: " + defaultValue; + assertThat(actual).as(assertionMessage).isEqualTo(expected); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/AnnotationProcessorCompilerErrorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/AnnotationProcessorCompilerErrorTest.java new file mode 100644 index 00000000000..98fe4b62f32 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/AnnotationProcessorCompilerErrorTest.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.logging.log4j.core.config.plugins.processor; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.logging.log4j.core.test.Compiler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junitpioneer.jupiter.Issue; + +@Issue("LOG4J2-3609") +// only enabled for explicit testing of what may be a javac bug +// mvn test -Dtest=AnnotationProcessorCompilerErrorTest'#'testMissingExternalAnnotationIsSupported +@EnabledIfSystemProperty( + named = "test", + matches = "AnnotationProcessorCompilerErrorTest#testMissingExternalAnnotationIsSupported") +class AnnotationProcessorCompilerErrorTest { + @Test + void testMissingExternalAnnotationIsSupported() throws Exception { + final Path sourceDirectory = Paths.get("target", "test-classes", "LOG4J2-3609"); + // set up classpath directories + final Path annotationDirectory = Files.createTempDirectory("annotation"); + final Path coreDirectory = Files.createTempDirectory("core"); + final Path bugDirectory = Files.createTempDirectory("bug"); + final Path processorDirectory = Files.createTempDirectory("processor"); + + // Create a stub annotation processor + final Path originalProcessor = sourceDirectory.resolve("MyAnnotationProcessor.java.source"); + final Path myProcessor = sourceDirectory.resolve("MyAnnotationProcessor.java"); + Files.move(originalProcessor, myProcessor); + try { + Compiler.compile(myProcessor.toFile(), "-proc:none", "-d", processorDirectory.toString()); + } finally { + Files.move(myProcessor, originalProcessor); + } + + // Compile the annotation and keep the class in a dedicated directory. + // javac -d target/annotation src/MyAnnotation.java + final Path originalAnnotation = sourceDirectory.resolve("MyAnnotation.java.source"); + final Path myAnnotation = sourceDirectory.resolve("MyAnnotation.java"); + Files.move(originalAnnotation, myAnnotation); + try { + Compiler.compile( + myAnnotation.toFile(), + "-d", + annotationDirectory.toString(), + "-processorpath", + processorDirectory.toString(), + "-processor", + "MyAnnotationProcessor"); + } finally { + Files.move(myAnnotation, originalAnnotation); + } + + // Compile the annotated class and save it in a dedicated directory. + // javac -d target/core -cp target/annotation src/MyAnnotatedClass.java + final Path originalAnnotatedClass = sourceDirectory.resolve("MyAnnotatedClass.java.source"); + final Path myAnnotatedClass = sourceDirectory.resolve("MyAnnotatedClass.java"); + Files.move(originalAnnotatedClass, myAnnotatedClass); + try { + Compiler.compile( + myAnnotatedClass.toFile(), + "-d", + coreDirectory.toString(), + "-cp", + annotationDirectory.toString(), + "-processorpath", + processorDirectory.toString(), + "-processor", + "MyAnnotationProcessor"); + } finally { + Files.move(myAnnotatedClass, originalAnnotatedClass); + } + + // Compile the empty subclass, which depends transitively on MyAnnotation, + // without including MyAnnotation.class in the classpath. + // javac -d target/bug -cp target/core:target/log4j/* src/MyEmptySubClass.java + final Path originalSubclass = sourceDirectory.resolve("MyEmptySubClass.java.source"); + final Path mySubclass = sourceDirectory.resolve("MyEmptySubClass.java"); + Files.move(originalSubclass, mySubclass); + try { + Compiler.compile( + mySubclass.toFile(), + "-d", + bugDirectory.toString(), + "-cp", + coreDirectory.toString(), + "-processorpath", + processorDirectory.toString(), + "-processor", + "MyAnnotationProcessor"); + } finally { + Files.move(mySubclass, originalSubclass); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakeAnnotations.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakeAnnotations.java new file mode 100644 index 00000000000..f51243c748d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakeAnnotations.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.config.plugins.processor; + +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.config.plugins.PluginVisitorStrategy; +import org.apache.logging.log4j.core.config.plugins.validation.Constraint; +import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; +import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; + +/** + * Fake constraint and plugin visitor that are accessed through reflection. + */ +public class FakeAnnotations { + + @Constraint(FakeConstraintValidator.class) + public @interface FakeConstraint {} + + public static class FakeConstraintValidator implements ConstraintValidator { + @Override + public void initialize(FakeConstraint annotation) {} + + @Override + public boolean isValid(String name, Object value) { + return false; + } + } + + @PluginVisitorStrategy(FakePluginVisitor.class) + public @interface FakeAnnotation {} + + public static class FakePluginVisitor implements PluginVisitor { + + @Override + public PluginVisitor setAnnotation(Annotation annotation) { + return null; + } + + @Override + public PluginVisitor setAliases(String... aliases) { + return null; + } + + @Override + public PluginVisitor setStrSubstitutor(StrSubstitutor substitutor) { + return null; + } + + @Override + public PluginVisitor setMember(Member member) { + return null; + } + + @Override + public Object visit(Configuration configuration, Node node, LogEvent event, StringBuilder log) { + return null; + } + + @Override + public PluginVisitor setConversionType(Class conversionType) { + return null; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java new file mode 100644 index 00000000000..0343f3bb358 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.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.config.plugins.processor; + +import java.io.Serializable; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +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.PluginAliases; +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.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.PluginLoggerContext; +import org.apache.logging.log4j.core.config.plugins.PluginNode; +import org.apache.logging.log4j.core.config.plugins.PluginValue; + +/** + * Test plugin class for unit tests. + */ +@Plugin(name = "Fake", category = "Test") +@PluginAliases({"AnotherFake", "StillFake"}) +public class FakePlugin { + + @Plugin(name = "Nested", category = "Test") + public static class Nested {} + + @PluginFactory + public static FakePlugin newPlugin( + @PluginAttribute("attribute") int attribute, + @PluginElement("layout") Layout layout, + @PluginConfiguration Configuration config, + @PluginNode Node node, + @PluginLoggerContext LoggerContext loggerContext, + @PluginValue("value") String value) { + return null; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private int attribute; + + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private int attributeWithoutPublicSetterButWithSuppressAnnotation; + + @PluginElement("layout") + private Layout layout; + + @PluginConfiguration + private Configuration config; + + @PluginNode + private Node node; + + @PluginLoggerContext + private LoggerContext loggerContext; + + @PluginValue("value") + private String value; + + @Override + public FakePlugin build() { + return null; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePluginPublicSetter.java.source b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePluginPublicSetter.java.source new file mode 100644 index 00000000000..045f03f0fcf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePluginPublicSetter.java.source @@ -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.config.plugins.processor; + +import java.io.Serializable; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +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.PluginAliases; +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.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.PluginLoggerContext; +import org.apache.logging.log4j.core.config.plugins.PluginNode; +import org.apache.logging.log4j.core.config.plugins.PluginValue; + +/** + * Test plugin class for unit tests. + */ +@Plugin(name = "Fake", category = "Test") +@PluginAliases({"AnotherFake", "StillFake"}) +public class FakePluginPublicSetter { + + @Plugin(name = "Nested", category = "Test") + public static class Nested {} + + @PluginFactory + public static FakePluginPublicSetter newPlugin( + @PluginAttribute("attribute") int attribute, + @PluginElement("layout") Layout layout, + @PluginConfiguration Configuration config, + @PluginNode Node node, + @PluginLoggerContext LoggerContext loggerContext, + @PluginValue("value") String value) { + return null; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + private int attribute; + + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private int attributeWithoutPublicSetterButWithSuppressAnnotation; + + @PluginElement("layout") + private Layout layout; + + @PluginConfiguration + private Configuration config; + + @PluginNode + private Node node; + + @PluginLoggerContext + private LoggerContext loggerContext; + + @PluginValue("value") + private String value; + + @Override + public FakePluginPublicSetter build() { + return null; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java new file mode 100644 index 00000000000..957bb3228cd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java @@ -0,0 +1,281 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +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.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +class GraalVmProcessorTest { + + private static final String FAKE_PLUGIN_NAME = "example.FakePlugin"; + private static final Object FAKE_PLUGIN = asMap( + "name", + FAKE_PLUGIN_NAME, + "methods", + asList( + asMap("name", "", "parameterTypes", emptyList()), + asMap( + "name", + "newPlugin", + "parameterTypes", + asList( + "int", + "org.apache.logging.log4j.core.Layout", + "org.apache.logging.log4j.core.config.Configuration", + "org.apache.logging.log4j.core.config.Node", + "org.apache.logging.log4j.core.LoggerContext", + "java.lang.String"))), + "fields", + emptyList()); + private static final String FAKE_PLUGIN_BUILDER_NAME = FAKE_PLUGIN_NAME + "$Builder"; + private static final Object FAKE_PLUGIN_BUILDER = asMap( + "name", + FAKE_PLUGIN_BUILDER_NAME, + "methods", + emptyList(), + "fields", + asList( + asMap("name", "attribute"), + asMap("name", "attributeWithoutPublicSetterButWithSuppressAnnotation"), + asMap("name", "config"), + asMap("name", "layout"), + asMap("name", "loggerContext"), + asMap("name", "node"), + asMap("name", "value"))); + private static final String FAKE_PLUGIN_NESTED_NAME = FAKE_PLUGIN_NAME + "$Nested"; + private static final Object FAKE_PLUGIN_NESTED = onlyNoArgsConstructor(FAKE_PLUGIN_NESTED_NAME); + private static final String FAKE_CONSTRAINT_VALIDATOR_NAME = "example.FakeAnnotations$FakeConstraintValidator"; + private static final Object FAKE_CONSTRAINT_VALIDATOR = onlyNoArgsConstructor(FAKE_CONSTRAINT_VALIDATOR_NAME); + private static final String FAKE_PLUGIN_VISITOR_NAME = "example.FakeAnnotations$FakePluginVisitor"; + private static final Object FAKE_PLUGIN_VISITOR = onlyNoArgsConstructor(FAKE_PLUGIN_VISITOR_NAME); + private static final String FAKE_CONVERTER_NAME = "example.FakeConverter"; + private static final Object FAKE_CONVERTER = asMap( + "name", + FAKE_CONVERTER_NAME, + "methods", + singletonList(asMap( + "name", + "newInstance", + "parameterTypes", + asList("org.apache.logging.log4j.core.config.Configuration", "java.lang.String[]"))), + "fields", + emptyList()); + + private static final String GROUP_ID = "groupId"; + private static final String ARTIFACT_ID = "artifactId"; + private static final String FALLBACK_METADATA_FOLDER = "fooBar"; + + /** + * Generates a metadata element with just a single no-arg constructor. + * + * @param className The name of the metadata element. + * @return A GraalVM metadata element. + */ + private static Object onlyNoArgsConstructor(String className) { + return asMap( + "name", + className, + "methods", + singletonList(asMap("name", "", "parameterTypes", emptyList())), + "fields", + emptyList()); + } + + private static Map asMap(Object... pairs) { + final Map map = new LinkedHashMap<>(); + if (pairs.length % 2 != 0) { + throw new IllegalArgumentException("odd number of arguments: " + pairs.length); + } + for (int i = 0; i < pairs.length; i += 2) { + map.put((String) pairs[i], pairs[i + 1]); + } + return map; + } + + private static Path sourceDir; + + @TempDir + private static Path outputDir; + + @BeforeAll + static void setup() throws Exception { + URL sourceUrl = requireNonNull(GraalVmProcessorTest.class.getResource("/GraalVmProcessorTest/java")); + sourceDir = Paths.get(sourceUrl.toURI()); + // Generate metadata + List diagnostics = generateDescriptor(sourceDir, GROUP_ID, ARTIFACT_ID, outputDir); + assertThat(diagnostics).isEmpty(); + } + + static Stream containsSpecificEntries() { + return Stream.of( + Arguments.of(FAKE_PLUGIN_NAME, FAKE_PLUGIN), + Arguments.of(FAKE_PLUGIN_BUILDER_NAME, FAKE_PLUGIN_BUILDER), + Arguments.of(FAKE_PLUGIN_NESTED_NAME, FAKE_PLUGIN_NESTED), + Arguments.of(FAKE_CONSTRAINT_VALIDATOR_NAME, FAKE_CONSTRAINT_VALIDATOR), + Arguments.of(FAKE_PLUGIN_VISITOR_NAME, FAKE_PLUGIN_VISITOR), + Arguments.of(FAKE_CONVERTER_NAME, FAKE_CONVERTER)); + } + + @ParameterizedTest + @MethodSource + void containsSpecificEntries(String className, Object expectedJson) throws IOException { + // Read metadata + Path reachabilityMetadataPath = + outputDir.resolve("META-INF/native-image/log4j-generated/groupId/artifactId/reflect-config.json"); + String reachabilityMetadata = new String(Files.readAllBytes(reachabilityMetadataPath), UTF_8); + assertThatJson(reachabilityMetadata) + .inPath(String.format("$[?(@.name == '%s')]", className)) + .isArray() + .hasSize(1) + .first() + .isEqualTo(json(expectedJson)); + } + + static Stream reachabilityMetadataPath() { + return Stream.of( + Arguments.of( + "groupId", + "artifactId", + "META-INF/native-image/log4j-generated/groupId/artifactId/reflect-config.json"), + Arguments.of(null, "artifactId", "META-INF/native-image/log4j-generated/fooBar/reflect-config.json"), + Arguments.of("groupId", null, "META-INF/native-image/log4j-generated/fooBar/reflect-config.json"), + Arguments.of(null, null, "META-INF/native-image/log4j-generated/fooBar/reflect-config.json")); + } + + @ParameterizedTest + @MethodSource + void reachabilityMetadataPath(@Nullable String groupId, @Nullable String artifactId, String expected) { + Messager messager = Mockito.mock(Messager.class); + Elements elements = Mockito.mock(Elements.class); + ProcessingEnvironment processingEnv = Mockito.mock(ProcessingEnvironment.class); + when(processingEnv.getMessager()).thenReturn(messager); + when(processingEnv.getElementUtils()).thenReturn(elements); + GraalVmProcessor processor = new GraalVmProcessor(); + processor.init(processingEnv); + assertThat(processor.getReachabilityMetadataPath(groupId, artifactId, FALLBACK_METADATA_FOLDER)) + .isEqualTo(expected); + } + + @Test + void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = CleanupMode.NEVER) Path outputDir) + throws Exception { + List diagnostics = generateDescriptor(sourceDir, null, null, outputDir); + assertThat(diagnostics).hasSize(1); + // The warning message should contain the information about the missing groupId and artifactId arguments + assertThat(diagnostics.get(0)) + .contains( + "recommended", + "-A" + GraalVmProcessor.GROUP_ID + "=", + "-A" + GraalVmProcessor.ARTIFACT_ID + "="); + Path path = outputDir.resolve("META-INF/native-image/log4j-generated"); + List reachabilityMetadataFolders; + try (Stream files = Files.list(path)) { + reachabilityMetadataFolders = files.filter(Files::isDirectory).collect(Collectors.toList()); + } + // The generated folder name should be deterministic and based solely on the descriptor content. + // If the descriptor changes, this test and the expected folder name must be updated accordingly. + assertThat(reachabilityMetadataFolders).hasSize(1).containsExactly(path.resolve("72c240aa")); + assertThat(reachabilityMetadataFolders.get(0).resolve("reflect-config.json")) + .as("Reachability metadata file") + .exists(); + } + + private static List generateDescriptor( + Path sourceDir, @Nullable String groupId, @Nullable String artifactId, Path outputDir) throws Exception { + // Instantiate the tooling + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8); + + // Populate sources + final Iterable sources; + try (final Stream files = Files.walk(sourceDir)) { + File[] sourceFiles = + files.filter(Files::isRegularFile).map(Path::toFile).toArray(File[]::new); + sources = fileManager.getJavaFileObjects(sourceFiles); + } + + // Set the target path used by `DescriptorGenerator` to dump the generated files + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(outputDir.toFile())); + + // Prepare the compiler options + List options = new ArrayList<>(); + options.add("-proc:only"); + options.add("-processor"); + options.add(GraalVmProcessor.class.getName()); + if (groupId != null) { + options.add("-A" + GraalVmProcessor.GROUP_ID + "=" + groupId); + } + if (artifactId != null) { + options.add("-A" + GraalVmProcessor.ARTIFACT_ID + "=" + artifactId); + } + + // Compile the sources + final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); + final JavaCompiler.CompilationTask task = + compiler.getTask(null, fileManager, diagnosticCollector, options, null, sources); + task.call(); + + // Verify successful compilation + return diagnosticCollector.getDiagnostics().stream() + .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) + .map(d -> d.getMessage(Locale.ROOT)) + // This message appears when the test runs on JDK 8 + .filter(m -> !"unknown enum constant java.lang.annotation.ElementType.MODULE".equals(m)) + .collect(Collectors.toList()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCacheTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCacheTest.java new file mode 100644 index 00000000000..c94fd5cb1fc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCacheTest.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.processor; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class PluginCacheTest { + + @Test + void testOutputIsReproducibleWhenInputOrderingChanges() throws IOException { + final PluginCache cacheA = new PluginCache(); + createCategory(cacheA, "one", Arrays.asList("bravo", "alpha", "charlie")); + createCategory(cacheA, "two", Arrays.asList("alpha", "charlie", "bravo")); + assertEquals(2, cacheA.getAllCategories().size()); + assertEquals(3, cacheA.getAllCategories().get("one").size()); + assertEquals(3, cacheA.getAllCategories().get("two").size()); + final PluginCache cacheB = new PluginCache(); + createCategory(cacheB, "two", Arrays.asList("bravo", "alpha", "charlie")); + createCategory(cacheB, "one", Arrays.asList("alpha", "charlie", "bravo")); + assertEquals(2, cacheB.getAllCategories().size()); + assertEquals(3, cacheB.getAllCategories().get("one").size()); + assertEquals(3, cacheB.getAllCategories().get("two").size()); + assertArrayEquals(cacheData(cacheA), cacheData(cacheB)); + } + + private void createCategory(final PluginCache cache, final String categoryName, final List entryNames) { + final Map category = cache.getCategory(categoryName); + for (String entryName : entryNames) { + final PluginEntry entry = new PluginEntry(); + entry.setKey(entryName); + entry.setClassName("com.example.Plugin"); + entry.setName("name"); + entry.setCategory(categoryName); + category.put(entryName, entry); + } + } + + private byte[] cacheData(final PluginCache cache) throws IOException { + final ByteArrayOutputStream outputB = new ByteArrayOutputStream(); + cache.writeCache(outputB); + return outputB.toByteArray(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.java new file mode 100644 index 00000000000..587490e2c92 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorPublicSetterTest.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.processor; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +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.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PluginProcessorPublicSetterTest { + + private static final String FAKE_PLUGIN_CLASS_PATH = + "src/test/java/org/apache/logging/log4j/core/config/plugins/processor/" + FakePlugin.class.getSimpleName() + + "PublicSetter.java.source"; + + private File createdFile; + private DiagnosticCollector diagnosticCollector; + private List> errorDiagnostics; + + @BeforeEach + void setup() { + // Instantiate the tooling + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + diagnosticCollector = new DiagnosticCollector<>(); + final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8); + + // Get the source files + Path sourceFile = Paths.get(FAKE_PLUGIN_CLASS_PATH); + + assertThat(sourceFile).exists(); + + final File orig = sourceFile.toFile(); + createdFile = new File(orig.getParentFile(), "FakePluginPublicSetter.java"); + assertDoesNotThrow(() -> FileUtils.copyFile(orig, createdFile)); + + // get compilation units + Iterable compilationUnits = fileManager.getJavaFileObjects(createdFile); + + JavaCompiler.CompilationTask task = compiler.getTask( + null, + fileManager, + diagnosticCollector, + Arrays.asList("-proc:only", "-processor", PluginProcessor.class.getName()), + null, + compilationUnits); + task.call(); + + errorDiagnostics = diagnosticCollector.getDiagnostics().stream() + .filter(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.ERROR) + .collect(Collectors.toList()); + } + + @AfterEach + void tearDown() { + assertDoesNotThrow(() -> FileUtils.delete(createdFile)); + File pluginsDatFile = Paths.get("Log4j2Plugins.dat").toFile(); + if (pluginsDatFile.exists()) { + pluginsDatFile.delete(); + } + } + + @Test + void warnWhenPluginBuilderAttributeLacksPublicSetter() { + + assertThat(errorDiagnostics).anyMatch(errorMessage -> errorMessage + .getMessage(Locale.ROOT) + .contains("The field `attribute` does not have a public setter")); + } + + @Test + void ignoreWarningWhenSuppressWarningsIsPresent() { + assertThat(errorDiagnostics) + .allMatch( + errorMessage -> !errorMessage + .getMessage(Locale.ROOT) + .contains( + "The field `attributeWithoutPublicSetterButWithSuppressAnnotation` does not have a public setter")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java new file mode 100644 index 00000000000..7dfad92ae8b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.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.config.plugins.processor; + +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.net.URL; +import java.util.Enumeration; +import java.util.Map; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAliases; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class PluginProcessorTest { + + private static final PluginCache pluginCache = new PluginCache(); + + private final Plugin p = FakePlugin.class.getAnnotation(Plugin.class); + + @BeforeAll + static void setUpClass() throws Exception { + final Enumeration resources = + PluginProcessor.class.getClassLoader().getResources(PluginProcessor.PLUGIN_CACHE_FILE); + pluginCache.loadCacheFiles(resources); + } + + @Test + void testTestCategoryFound() { + assertNotNull(p, "No plugin annotation on FakePlugin."); + final Map testCategory = pluginCache.getCategory(p.category()); + assertNotEquals(0, pluginCache.size(), "No plugins were found."); + assertNotNull(testCategory, "The category '" + p.category() + "' was not found."); + assertFalse(testCategory.isEmpty()); + } + + @Test + void testFakePluginFoundWithCorrectInformation() { + final PluginEntry fake = pluginCache.getCategory(p.category()).get(toRootLowerCase(p.name())); + verifyFakePluginEntry(p.name(), fake); + } + + @Test + void testFakePluginAliasesContainSameInformation() { + final PluginAliases aliases = FakePlugin.class.getAnnotation(PluginAliases.class); + for (final String alias : aliases.value()) { + final PluginEntry fake = pluginCache.getCategory(p.category()).get(toRootLowerCase(alias)); + verifyFakePluginEntry(alias, fake); + } + } + + private void verifyFakePluginEntry(final String name, final PluginEntry fake) { + assertNotNull(fake, "The plugin '" + toRootLowerCase(name) + "' was not found."); + assertEquals(FakePlugin.class.getName(), fake.getClassName()); + assertEquals(toRootLowerCase(name), fake.getKey()); + assertEquals(Plugin.EMPTY, p.elementType()); + assertEquals(name, fake.getName()); + assertEquals(p.printObject(), fake.isPrintable()); + assertEquals(p.deferChildren(), fake.isDefer()); + } + + @Test + void testNestedPlugin() { + final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class); + final PluginEntry nested = pluginCache.getCategory(p.category()).get(toRootLowerCase(p.name())); + assertNotNull(nested); + assertEquals(toRootLowerCase(p.name()), nested.getKey()); + assertEquals(FakePlugin.Nested.class.getName(), nested.getClassName()); + assertEquals(p.name(), nested.getName()); + assertEquals(Plugin.EMPTY, p.elementType()); + assertEquals(p.printObject(), nested.isPrintable()); + assertEquals(p.deferChildren(), nested.isDefer()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerTest.java new file mode 100644 index 00000000000..683756b0d68 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerTest.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.logging.log4j.core.config.plugins.util; + +import static java.util.Collections.emptyEnumeration; +import static java.util.Collections.enumeration; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Node; +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.config.xml.XmlConfigurationFactory; +import org.apache.logging.log4j.core.test.Compiler; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.util.LoaderUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PluginManagerTest { + + @BeforeEach + void setUp() { + PluginRegistry.getInstance().clear(); + } + + @Test + @UsingStatusListener + @SuppressWarnings("deprecation") + void calling_addPackage_issues_warning(final ListStatusListener listener) { + try { + PluginManager.addPackage("com.example"); + assertThat(listener.findStatusData(Level.WARN)) + .anySatisfy(PluginManagerTest::assertContainsPackageScanningLink); + listener.clear(); + + PluginManager.addPackages(singletonList("com.example")); + assertThat(listener.findStatusData(Level.WARN)) + .anySatisfy(PluginManagerTest::assertContainsPackageScanningLink); + listener.clear(); + } finally { + PluginManager.clearPackages(); + } + } + + @Test + @UsingStatusListener + void using_packages_issues_warning(final ListStatusListener listener) { + final ConfigurationSource source = ConfigurationSource.fromResource( + "customplugin/log4j2-741.xml", getClass().getClassLoader()); + assertThat(source).as("Configuration source").isNotNull(); + final Configuration configuration = new XmlConfigurationFactory().getConfiguration(null, source); + assertThat(configuration).as("Configuration").isNotNull(); + configuration.initialize(); + assertThat(configuration.getPluginPackages()).as("Plugin packages").contains("customplugin"); + assertThat(listener.findStatusData(Level.WARN)) + .anySatisfy(PluginManagerTest::assertContainsPackageScanningLink); + } + + @Test + @UsingStatusListener + void using_packages_finds_custom_plugin(final ListStatusListener listener) throws Exception { + // To ensure our custom plugin is NOT included in the log4j plugin metadata file, + // we make sure the class does not exist until after the build is finished. + // So we don't create the custom plugin class until this test is run. + final URL resource = PluginManagerTest.class.getResource("/customplugin/FixedStringLayout.java.source"); + assertThat(resource).isNotNull(); + final File orig = new File(resource.toURI()); + final File f = new File(orig.getParentFile(), "FixedStringLayout.java"); + assertDoesNotThrow(() -> FileUtils.copyFile(orig, f)); + // compile generated source + // (switch off annotation processing: no need to create Log4j2Plugins.dat) + Compiler.compile(f, "-proc:none"); + assertDoesNotThrow(() -> FileUtils.delete(f)); + // load the compiled class + Class.forName("customplugin.FixedStringLayout"); + + final PluginManager manager = new PluginManager(Node.CATEGORY); + manager.collectPlugins(singletonList("customplugin")); + assertThat(manager.getPluginType("FixedString")) + .as("Custom unregistered plugin") + .isNotNull(); + assertThat(listener.findStatusData(Level.WARN)).anySatisfy(message -> { + assertThat(message.getLevel()).isEqualTo(Level.WARN); + assertThat(message.getMessage().getFormattedMessage()) + .as("Status logger message") + // The message specifies which plugin is not registered + .contains("customplugin.FixedStringLayout") + // The message provides a link to the registration instructions + .contains("https://logging.apache.org/log4j/2.x/manual/plugins.html#plugin-registry"); + }); + } + + @Test + @UsingStatusListener + void missing_plugin_descriptor_issues_warning(final ListStatusListener listener) { + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread() + .setContextClassLoader(new FilteringClassLoader(getClass().getClassLoader(), null)); + assertThat(LoaderUtil.getClassLoader().getResource(PluginProcessor.PLUGIN_CACHE_FILE)) + .isNull(); + final PluginManager manager = new PluginManager(Node.CATEGORY); + manager.collectPlugins(null); + assertThat(listener.findStatusData(Level.WARN)).anySatisfy(message -> { + assertThat(message.getLevel()).isEqualTo(Level.WARN); + assertThat(message.getMessage().getFormattedMessage()) + .as("Status logger message") + // The message provides a link to plugin descriptor troubleshooting. + .contains("https://logging.apache.org/log4j/2.x/faq.html#plugin-descriptors"); + }); + } finally { + Thread.currentThread().setContextClassLoader(tccl); + } + } + + /** + * Tests if removing descriptors of standard Log4j artifacts and replacing them with a custom one issues warnings. + *

+ * This often happens in shading scenarios. + *

+ */ + @Test + @UsingStatusListener + void corrupted_plugin_descriptor_issues_warning(final ListStatusListener listener) throws IOException { + // Create empty descriptor + final Path customDescriptor = Files.createTempFile("Log4j2Plugins", ".dat"); + final URL descriptorUrl = customDescriptor.toUri().toURL(); + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + // Generate a plugin entry to have a non-empty plugin descriptor + // For this test case it doesn't really matter if the class exists or not. + // This test will not instantiate the plugin. + final PluginEntry entry = new PluginEntry(); + entry.setKey("CustomPlugin"); + entry.setClassName("com.example.NotExistingClass"); + entry.setName("CustomPlugin"); + entry.setCategory(Node.CATEGORY); + // Generate descriptor with single plugin + final PluginCache customCache = new PluginCache(); + customCache.getCategory(Node.CATEGORY).put("CustomPlugin", entry); + customCache.writeCache(Files.newOutputStream(customDescriptor)); + + Thread.currentThread() + .setContextClassLoader(new FilteringClassLoader(getClass().getClassLoader(), descriptorUrl)); + + assertThat(LoaderUtil.getClassLoader().getResource(PluginProcessor.PLUGIN_CACHE_FILE)) + .isEqualTo(descriptorUrl); + final PluginManager manager = new PluginManager(Node.CATEGORY); + // Try loading some standard Log4j plugins + manager.collectPlugins(singletonList("org.apache.logging.log4j")); + assertThat(manager.getPluginType("Console")).isNotNull(); + assertThat(listener.findStatusData(Level.WARN)).anySatisfy(message -> { + assertThat(message.getLevel()).isEqualTo(Level.WARN); + assertThat(message.getMessage().getFormattedMessage()) + .as("Status logger message") + // The message specifies which standard plugin is not registered + .contains("org.apache.logging.log4j.core.appender.ConsoleAppender") + // The message provides a link to plugin descriptor troubleshooting. + .contains("https://logging.apache.org/log4j/2.x/faq.html#plugin-descriptors"); + }); + } finally { + Thread.currentThread().setContextClassLoader(tccl); + Files.delete(customDescriptor); + } + } + + private static void assertContainsPackageScanningLink(final StatusData message) { + assertThat(message.getLevel()).isEqualTo(Level.WARN); + assertThat(message.getMessage().getFormattedMessage()) + // The message specifies + .contains("https://logging.apache.org/log4j/2.x/faq.html#package-scanning"); + } + + private static final class FilteringClassLoader extends ClassLoader { + + private final URL descriptorUrl; + + private FilteringClassLoader(final ClassLoader parent, final URL descriptorUrl) { + super(parent); + this.descriptorUrl = descriptorUrl; + } + + @Override + public URL getResource(final String name) { + return PluginProcessor.PLUGIN_CACHE_FILE.equals(name) ? descriptorUrl : super.getResource(name); + } + + @Override + public Enumeration getResources(final String name) throws IOException { + return PluginProcessor.PLUGIN_CACHE_FILE.equals(name) + ? descriptorUrl != null ? enumeration(singletonList(descriptorUrl)) : emptyEnumeration() + : super.getResources(name); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java new file mode 100644 index 00000000000..262af0b0350 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.Proxy; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.stream.Stream; +import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry.PluginTest; +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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests the ResolverUtil class for custom protocol like bundleresource, vfs, vfszip. + */ +class ResolverUtilCustomProtocolTest { + + static class NoopURLStreamHandlerFactory implements URLStreamHandlerFactory { + + @Override + public URLStreamHandler createURLStreamHandler(final String protocol) { + return new URLStreamHandler() { + @Override + protected URLConnection openConnection(final URL url) { + return open(url, null); + } + + private URLConnection open(final URL url, final Proxy proxy) { + return new URLConnection(url) { + @Override + public void connect() { + // do nothing + } + }; + } + + @Override + protected URLConnection openConnection(final URL url, final Proxy proxy) { + return open(url, proxy); + } + + @Override + protected int getDefaultPort() { + return 1; + } + }; + } + } + + private static final String DIR = "target/vfs"; + private static Field factoryField; + private static URLStreamHandlerFactory oldFactory; + + @BeforeAll + static void setup() throws Exception { + for (final Field field : URL.class.getDeclaredFields()) { + if (URLStreamHandlerFactory.class.equals(field.getType()) && "factory".equals(field.getName())) { + factoryField = field; + factoryField.setAccessible(true); + oldFactory = (URLStreamHandlerFactory) factoryField.get(null); + } + } + assertThat(factoryField).as("java.net.URL#factory field").isNotNull(); + URL.setURLStreamHandlerFactory(new NoopURLStreamHandlerFactory()); + } + + @AfterAll + static void cleanup() throws Exception { + final Field handlersFields = URL.class.getDeclaredField("handlers"); + if (handlersFields != null) { + if (!handlersFields.isAccessible()) { + handlersFields.setAccessible(true); + } + @SuppressWarnings("unchecked") + final Hashtable handlers = + (Hashtable) handlersFields.get(null); + if (handlers != null) { + handlers.clear(); + } + } + factoryField.set(null, null); + URL.setURLStreamHandlerFactory(oldFactory); + } + + static class SingleURLClassLoader extends ClassLoader { + private final URL url; + + public SingleURLClassLoader(final URL url) { + this.url = url; + } + + public SingleURLClassLoader(final URL url, final ClassLoader parent) { + super(parent); + this.url = url; + } + + @Override + protected URL findResource(final String name) { + return url; + } + + @Override + public URL getResource(final String name) { + return findResource(name); + } + + @Override + public Enumeration getResources(final String name) throws IOException { + return findResources(name); + } + + @Override + protected Enumeration findResources(final String name) { + return Collections.enumeration(Arrays.asList(findResource(name))); + } + } + + static Stream testExtractedPath() { + return Stream.of( + Arguments.of( + "vfs:/C:/jboss/jboss-eap-6.4/standalone/deployments/com.xxx.yyy.application-ear.ear/lib/com.xxx.yyy.logging.jar/com/xxx/yyy/logging/config/", + "/C:/jboss/jboss-eap-6.4/standalone/deployments/com.xxx.yyy.application-ear.ear/lib/com.xxx.yyy.logging.jar/com/xxx/yyy/logging/config/"), + Arguments.of( + "vfs:/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/", + "/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/"), + Arguments.of( + "vfs:/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/", + "/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/"), + Arguments.of( + "vfszip:/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd", + "/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd"), + Arguments.of( + "vfs:/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/", + "/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/"), + Arguments.of( + "vfszip:/path+with+plus/file+name+with+plus.xml", "/path+with+plus/file+name+with+plus.xml"), + Arguments.of("vfs:/path+with+plus/file+name+with+plus.xml", "/path+with+plus/file+name+with+plus.xml"), + Arguments.of("bundleresource:/some/path/some/file.properties", "/some/path/some/file.properties"), + Arguments.of("bundleresource:/some+path/some+file.properties", "/some+path/some+file.properties")); + } + + @ParameterizedTest + @MethodSource + void testExtractedPath(final String urlAsString, final String expected) throws Exception { + final URL url = new URL(urlAsString); + assertThat(new ResolverUtil().extractPath(url)).isEqualTo(expected); + } + + @Test + void testFindInPackageFromVfsDirectoryURL() throws Exception { + final File tmpDir = new File(DIR, "resolverutil3"); + try (final URLClassLoader cl = ResolverUtilTest.compileAndCreateClassLoader(tmpDir, "3")) { + final ResolverUtil resolverUtil = new ResolverUtil(); + resolverUtil.setClassLoader(new SingleURLClassLoader(new URL("vfs:/" + tmpDir + "/customplugin3/"), cl)); + resolverUtil.findInPackage(new PluginTest(), "customplugin3"); + assertEquals(1, resolverUtil.getClasses().size(), "Class not found in packages"); + assertEquals( + cl.loadClass("customplugin3.FixedString3Layout"), + resolverUtil.getClasses().iterator().next(), + "Unexpected class resolved"); + } + } + + @Test + void testFindInPackageFromVfsJarURL() throws Exception { + final File tmpDir = new File(DIR, "resolverutil4"); + try (final URLClassLoader cl = ResolverUtilTest.compileJarAndCreateClassLoader(tmpDir, "4")) { + final ResolverUtil resolverUtil = new ResolverUtil(); + resolverUtil.setClassLoader( + new SingleURLClassLoader(new URL("vfs:/" + tmpDir + "/customplugin4.jar/customplugin4/"), cl)); + resolverUtil.findInPackage(new PluginTest(), "customplugin4"); + assertEquals(1, resolverUtil.getClasses().size(), "Class not found in packages"); + assertEquals( + cl.loadClass("customplugin4.FixedString4Layout"), + resolverUtil.getClasses().iterator().next(), + "Unexpected class resolved"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java new file mode 100644 index 00000000000..0ff21e20567 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry.PluginTest; +import org.apache.logging.log4j.core.test.Compiler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests the ResolverUtil class. + */ +class ResolverUtilTest { + + static Stream testExtractedPath() { + return Stream.of( + Arguments.of( + "jar:file:/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar!/org/junit/Test.class", + "/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar"), + Arguments.of( + "jar:file:/path+with+plus/file+does+not+exist.jar!/some/file", + "/path with plus/file does not exist.jar"), + Arguments.of( + "file:/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml", + "/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml"), + Arguments.of( + "file:///path+with+plus/file+does+not+exist.xml", "/path with plus/file does not exist.xml"), + Arguments.of("http://java.sun.com/index.html#chapter1", "/index.html"), + Arguments.of( + "http://www.server.com/path+with+plus/file+name+with+plus.jar!/org/junit/Test.class", + "/path with plus/file name with plus.jar"), + Arguments.of( + "https://issues.apache.org/jira/browse/LOG4J2-445?focusedCommentId=13862479&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13862479", + "/jira/browse/LOG4J2-445"), + Arguments.of( + "ftp://user001:secretpassword@private.ftp-servers.example.com/mydirectory/myfile.txt", + "/mydirectory/myfile.txt"), + Arguments.of( + "ftp://user001:secretpassword@private.ftp-servers.example.com/my+directory/my+file.txt", + "/my directory/my file.txt")); + } + + @ParameterizedTest + @MethodSource + void testExtractedPath(final String urlAsString, final String expected) throws Exception { + final URL url = new URL(urlAsString); + assertThat(new ResolverUtil().extractPath(url)).isEqualTo(expected); + } + + @Test + void testExtractPathFromJarUrlNotDecodedIfFileExists() throws Exception { + testExtractPathFromJarUrlNotDecodedIfFileExists("/log4j+config+with+plus+characters.xml"); + } + + private void testExtractPathFromJarUrlNotDecodedIfFileExists(final String existingFile) + throws MalformedURLException, UnsupportedEncodingException, URISyntaxException { + URL url = ResolverUtilTest.class.getResource(existingFile); + if (!"jar".equals(url.getProtocol())) { + // create fake jar: URL that resolves to existing file + url = new URL("jar:" + url.toExternalForm() + "!/some/entry"); + } + final String actual = new ResolverUtil().extractPath(url); + assertThat(actual).as("Not decoded path").endsWith(existingFile); + } + + @Test + void testFileFromUriWithSpacesAndPlusCharactersInName() throws Exception { + final String existingFile = "/s p a c e s/log4j+config+with+plus+characters.xml"; + testExtractPathFromJarUrlNotDecodedIfFileExists(existingFile); + } + + @Test + void testExtractPathFromFileUrlNotDecodedIfFileExists() throws Exception { + final String existingFile = "/log4j+config+with+plus+characters.xml"; + final URL url = ResolverUtilTest.class.getResource(existingFile); + assertThat(url).hasProtocol("file"); + final String actual = new ResolverUtil().extractPath(url); + assertThat(actual).endsWith(existingFile); + } + + @Test + void testFindInPackageFromDirectoryPath(final @TempDir File tmpDir) throws Exception { + try (final URLClassLoader cl = compileAndCreateClassLoader(tmpDir, "1")) { + final ResolverUtil resolverUtil = new ResolverUtil(); + resolverUtil.setClassLoader(cl); + resolverUtil.findInPackage(new PluginTest(), "customplugin1"); + assertThat(resolverUtil.getClasses()) + .as("Number of plugin classes") + .hasSize(1) + .as("Plugin class") + .contains(cl.loadClass("customplugin1.FixedString1Layout")); + } + } + + @Test + void testFindInPackageFromJarPath(final @TempDir File tmpDir) throws Exception { + try (final URLClassLoader cl = compileJarAndCreateClassLoader(tmpDir, "2")) { + final ResolverUtil resolverUtil = new ResolverUtil(); + resolverUtil.setClassLoader(cl); + resolverUtil.findInPackage(new PluginTest(), "customplugin2"); + assertThat(resolverUtil.getClasses()) + .as("Number of plugin classes") + .hasSize(1) + .as("Plugin class") + .contains(cl.loadClass("customplugin2.FixedString2Layout")); + } + } + + static URLClassLoader compileJarAndCreateClassLoader(final File tmpDir, final String suffix) throws Exception { + compile(tmpDir, suffix); + final File jarFile = new File(tmpDir, "customplugin" + suffix + ".jar"); + final URI jarURI = jarFile.toURI(); + createJar(jarURI, tmpDir, new File(tmpDir, "customplugin" + suffix + "/FixedString" + suffix + "Layout.class")); + return URLClassLoader.newInstance(new URL[] {jarURI.toURL()}); + } + + static URLClassLoader compileAndCreateClassLoader(final File tmpDir, final String suffix) throws Exception { + compile(tmpDir, suffix); + return URLClassLoader.newInstance(new URL[] {tmpDir.toURI().toURL()}); + } + + static void compile(final File tmpDir, final String suffix) throws Exception { + final URL resource = ResolverUtilTest.class.getResource("/customplugin/FixedStringLayout.java.source"); + assertThat(resource).isNotNull(); + final File orig = new File(resource.toURI()); + final File f = new File(tmpDir, "customplugin" + suffix + "/FixedString" + suffix + "Layout.java"); + final File parent = f.getParentFile(); + if (!parent.exists()) { + assertTrue(f.getParentFile().mkdirs(), "Create customplugin" + suffix + " folder KO"); + } + + final String content = new String(Files.readAllBytes(orig.toPath())) + .replaceAll("FixedString", "FixedString" + suffix) + .replaceAll("customplugin", "customplugin" + suffix); + Files.write(f.toPath(), content.getBytes()); + + // compile generated source + // (switch off annotation processing: no need to create Log4j2Plugins.dat) + Compiler.compile(f, "-proc:none"); + } + + static void createJar(final URI jarURI, final File workDir, final File f) throws Exception { + final Map env = new HashMap<>(); + env.put("create", "true"); + final URI uri = URI.create("jar:file://" + jarURI.getRawPath()); + try (final FileSystem zipfs = FileSystems.newFileSystem(uri, env)) { + final Path path = + zipfs.getPath(workDir.toPath().relativize(f.toPath()).toString()); + if (path.getParent() != null) { + Files.createDirectories(path.getParent()); + } + Files.copy(f.toPath(), path, StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java index 7b2a0f8a774..a900fdbfe47 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.validation; @@ -24,7 +24,7 @@ */ public class AbstractPluginWithGenericBuilder { - public static abstract class Builder> { + public abstract static class Builder> { @PluginBuilderAttribute @Required(message = "The thing given by the builder is null") @@ -39,17 +39,15 @@ public String getThing() { return thing; } - public B withThing(final String name) { + public B setThing(final String name) { this.thing = name; return asBuilder(); } - } private final String thing; public AbstractPluginWithGenericBuilder(final String thing) { - super(); this.thing = thing; } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.java new file mode 100644 index 00000000000..1db78e26b29 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.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.config.plugins.validation; + +import java.net.InetSocketAddress; +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.validation.constraints.ValidHost; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; + +@Plugin(name = "HostAndPort", category = "Test") +public final class HostAndPort { + + private final InetSocketAddress address; + + private HostAndPort(final InetSocketAddress address) { + this.address = address; + } + + public boolean isValid() { + return !address.isUnresolved(); + } + + @PluginFactory + public static HostAndPort createPlugin( + @ValidHost(message = "Unit test (host)") @PluginAttribute("host") final String host, + @ValidPort(message = "Unit test (port)") @PluginAttribute("port") final int port) { + return new HostAndPort(new InetSocketAddress(host, port)); + } + + @Override + public String toString() { + return "HostAndPort{" + "address=" + address + '}'; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java index aed8eeefeb0..7e5d284927d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.validation; @@ -45,11 +45,10 @@ public String getFoo1() { return foo1; } - public B withFoo1(final String foo1) { + public B setFoo1(final String foo1) { this.foo1 = foo1; return asBuilder(); } - } @PluginBuilderFactory @@ -67,5 +66,4 @@ public PluginWithGenericSubclassFoo1Builder(final String thing, final String foo public String getFoo1() { return foo1; } - } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java index 6068f46a3d2..0b86b929554 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.config.plugins.validation; import java.util.Objects; - 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; @@ -42,7 +41,7 @@ public String getName() { @PluginFactory public static ValidatingPlugin newValidatingPlugin( - @Required(message = "The name given by the factory is null") final String name) { + @Required(message = "The name given by the factory is null") final String name) { return new ValidatingPlugin(name); } @@ -57,7 +56,7 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @Required(message = "The name given by the builder is null") private String name; - public Builder withName(final String name) { + public Builder setName(final String name) { this.name = name; return this; } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java index 5ca9b6e4989..baed0712119 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.config.plugins.validation; import java.util.Objects; - 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; @@ -42,7 +41,7 @@ public String getName() { @PluginFactory public static ValidatingPluginWithGenericBuilder newValidatingPlugin( - @Required(message = "The name given by the factory is null") final String name) { + @Required(message = "The name given by the factory is null") final String name) { return new ValidatingPluginWithGenericBuilder(name); } @@ -51,13 +50,14 @@ public static > B newBuilder() { return new Builder().asBuilder(); } - public static class Builder> implements org.apache.logging.log4j.core.util.Builder { + public static class Builder> + implements org.apache.logging.log4j.core.util.Builder { @PluginBuilderAttribute @Required(message = "The name given by the builder is null") private String name; - public B withName(final String name) { + public B setName(final String name) { this.name = name; return asBuilder(); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java index 9ebb85e0910..ed2f3572456 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.config.plugins.validation; import java.util.Objects; - 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; @@ -42,7 +41,7 @@ public String getName() { @PluginFactory public static ValidatingPluginWithTypedBuilder newValidatingPlugin( - @Required(message = "The name given by the factory is null") final String name) { + @Required(message = "The name given by the factory is null") final String name) { return new ValidatingPluginWithTypedBuilder(name); } @@ -51,13 +50,14 @@ 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.core.util.Builder { @PluginBuilderAttribute @Required(message = "The name given by the builder is null") private String name; - public Builder withName(final String name) { + public Builder setName(final String name) { this.name = name; return this; } 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..9a53b6fced7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.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.plugins.validation.validators; + +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 org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.ValidatingPlugin; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class RequiredValidatorTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) manager.getPluginType("Validator"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + void testNullDefaultValue() { + final ValidatingPlugin validatingPlugin = (ValidatingPlugin) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + assertNull(validatingPlugin); + } + + @Test + void testNonNullValue() { + node.getAttributes().put("name", "foo"); + final ValidatingPlugin validatingPlugin = (ValidatingPlugin) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + assertNotNull(validatingPlugin); + 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..772c6a62d5f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.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.plugins.validation.validators; + +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 org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.HostAndPort; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@StatusLoggerLevel("FATAL") +class ValidHostValidatorTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) manager.getPluginType("HostAndPort"); + assertNotNull(plugin, "Rebuild this module to ensure annotation processing has been done."); + node = new Node(null, "HostAndPort", plugin); + } + + @Test + void testNullHost() { + assertNull(buildPlugin()); + } + + @Test + void testInvalidIpAddress() { + node.getAttributes().put("host", "256.256.256.256"); + node.getAttributes().put("port", "1"); + final HostAndPort plugin = buildPlugin(); + assertNull(plugin, "Expected null, but got: " + plugin); + } + + @Test + void testLocalhost() { + node.getAttributes().put("host", "localhost"); + node.getAttributes().put("port", "1"); + final HostAndPort hostAndPort = buildPlugin(); + assertNotNull(hostAndPort); + assertTrue(hostAndPort.isValid()); + } + + private HostAndPort buildPlugin() { + return (HostAndPort) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + } +} 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..99d4853233c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.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.config.plugins.validation.validators; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.HostAndPort; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ValidPortValidatorTest { + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) manager.getPluginType("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 + void testNegativePort() { + node.getAttributes().put("port", "-1"); + assertNull(buildPlugin()); + } + + @Test + void testValidPort() { + node.getAttributes().put("port", "10"); + assertNotNull(buildPlugin()); + } + + @Test + void testInvalidPort() { + node.getAttributes().put("port", "1234567890"); + assertNull(buildPlugin()); + } + + private HostAndPort buildPlugin() { + return (HostAndPort) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + } +} 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..d47ba5457c1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.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.config.plugins.validation.validators; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyCollectionOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.ArrayList; +import java.util.List; +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.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ValidatingPluginWithFailoverTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager(Core.CATEGORY_NAME); + manager.collectPlugins(); + plugin = (PluginType) manager.getPluginType("failover"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + + final AppenderRef appenderRef = AppenderRef.createAppenderRef("List", Level.ALL, null); + node = new Node(null, "failover", plugin); + final Node failoversNode = new Node(node, "Failovers", manager.getPluginType("Failovers")); + final Node appenderRefNode = new Node(failoversNode, "appenderRef", manager.getPluginType("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 + void testDoesNotLog_NoParameterThatMatchesElement_message() { + final StoringStatusListener listener = new StoringStatusListener(); + // @formatter:off + final PluginBuilder builder = new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node); + // @formatter:on + StatusLogger.getLogger().registerListener(listener); + + final FailoverAppender failoverAppender = (FailoverAppender) builder.build(); + + assertThat(listener.logs, emptyCollectionOf(StatusData.class)); + assertNotNull(failoverAppender); + assertEquals("Failover", failoverAppender.getName()); + } + + private static class StoringStatusListener implements StatusListener { + private final List logs = new ArrayList<>(); + + @Override + public void log(final StatusData data) { + logs.add(data); + } + + @Override + public Level getStatusLevel() { + return Level.WARN; + } + + @Override + public void close() {} + } +} 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..6c6090438ff --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.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.validation.validators; + +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 org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.ValidatingPluginWithGenericBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ValidatingPluginWithGenericBuilderTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) + manager.getPluginType("ValidatingPluginWithGenericBuilder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + void testNullDefaultValue() { + final ValidatingPluginWithGenericBuilder validatingPlugin = + (ValidatingPluginWithGenericBuilder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + assertNull(validatingPlugin); + } + + @Test + void testNonNullValue() { + node.getAttributes().put("name", "foo"); + final ValidatingPluginWithGenericBuilder validatingPlugin = + (ValidatingPluginWithGenericBuilder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + 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..e3360258b7d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.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.config.plugins.validation.validators; + +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 org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.PluginWithGenericSubclassFoo1Builder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ValidatingPluginWithGenericSubclassFoo1BuilderTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) + manager.getPluginType("PluginWithGenericSubclassFoo1Builder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + void testNullDefaultValue() { + final PluginWithGenericSubclassFoo1Builder validatingPlugin = + (PluginWithGenericSubclassFoo1Builder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + assertNull(validatingPlugin); + } + + @Test + void testNonNullValue() { + node.getAttributes().put("thing", "thing1"); + node.getAttributes().put("foo1", "foo1"); + final PluginWithGenericSubclassFoo1Builder validatingPlugin = + (PluginWithGenericSubclassFoo1Builder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + 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..579be80385e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.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.config.plugins.validation.validators; + +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 org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.config.plugins.validation.ValidatingPluginWithTypedBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ValidatingPluginWithTypedBuilderTest { + + private PluginType plugin; + private Node node; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + final PluginManager manager = new PluginManager("Test"); + manager.collectPlugins(); + plugin = (PluginType) + manager.getPluginType("ValidatingPluginWithTypedBuilder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + void testNullDefaultValue() { + // @formatter:off + final ValidatingPluginWithTypedBuilder validatingPlugin = + (ValidatingPluginWithTypedBuilder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + // @formatter:on + assertNull(validatingPlugin); + } + + @Test + void testNonNullValue() { + node.getAttributes().put("name", "foo"); + // @formatter:off + final ValidatingPluginWithTypedBuilder validatingPlugin = + (ValidatingPluginWithTypedBuilder) new PluginBuilder(plugin) + .withConfiguration(new NullConfiguration()) + .withConfigurationNode(node) + .build(); + // @formatter:on + 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..181879489ca --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.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; + +class PropertiesConfigurationTest { + + @Test + @LoggerContextSource("log4j2-properties.properties") + void testPropertiesConfiguration(final Configuration config) { + assertEquals(LifeCycle.State.STARTED, config.getState(), "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"); + assertInstanceOf(ThresholdFilter.class, filter, "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(LifeCycle.State.STARTED, config.getState(), "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(1, loggers.size(), "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(LifeCycle.State.STARTED, config.getState(), "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(3, 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"); + 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(LifeCycle.State.STARTED, config.getState(), "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"); + 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-test/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 new file mode 100644 index 00000000000..3437f787b8a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.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.config.xml; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import edu.umd.cs.findbugs.annotations.Nullable; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.status.StatusConsoleListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingStatusLoggerMock; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@UsingStatusLoggerMock +@ExtendWith(MockitoExtension.class) +class XmlConfigurationPropsTest { + + private static final String CONFIG_NAME = "XmlConfigurationPropsTest"; + + private static final String CONFIG1 = "org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest1.xml"; + + private static final String CONFIG1_NAME = "XmlConfigurationPropsTest1"; + + private void testConfiguration( + final Configuration config, + final String expectedConfigName, + @Nullable final Level expectedStatusLevel, + @Nullable final Level expectedRootLevel) { + assertThat(config) + .isInstanceOf(XmlConfiguration.class) + .extracting(Configuration::getName) + .isEqualTo(expectedConfigName); + final StatusConsoleListener fallbackListener = StatusLogger.getLogger().getFallbackListener(); + if (expectedStatusLevel == null) { + verify(fallbackListener, never()).setLevel(any()); + } else { + verify(fallbackListener).setLevel(eq(expectedStatusLevel)); + } + assertThat(config.getRootLogger().getExplicitLevel()).isEqualTo(expectedRootLevel); + } + + @Test + @SetTestProperty(key = "status.level", value = "using gibberish values to enforce defaults") + @SetTestProperty(key = "root.level", value = "using gibberish values to enforce defaults") + @LoggerContextSource + void testNoProps(final Configuration config) { + testConfiguration(config, CONFIG_NAME, null, null); + } + + @Test + @SetTestProperty(key = StatusLogger.DEFAULT_STATUS_LISTENER_LEVEL, value = "INFO") + @SetTestProperty(key = Constants.LOG4J_DEFAULT_STATUS_LEVEL, value = "WARN") + @LoggerContextSource(value = CONFIG1) + void testDefaultStatus(final Configuration config) { + testConfiguration(config, CONFIG1_NAME, Level.INFO, null); + } + + @Test + @SetTestProperty(key = Constants.LOG4J_DEFAULT_STATUS_LEVEL, value = "WARN") + @LoggerContextSource(value = CONFIG1) + void testDeprecatedDefaultStatus(final Configuration config) { + testConfiguration(config, CONFIG1_NAME, Level.WARN, null); + } + + @Test + @SetTestProperty(key = "status.level", value = "INFO") + @LoggerContextSource + void testWithConfigProp(final Configuration config) { + testConfiguration(config, CONFIG_NAME, Level.INFO, null); + } + + @Test + @SetTestProperty(key = "status.level", value = "INFO") + @SetTestProperty(key = "root.level", value = "DEBUG") + @LoggerContextSource + void testWithProps(final Configuration config) { + testConfiguration(config, CONFIG_NAME, Level.INFO, Level.DEBUG); + } +} 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..64aa3aa4d63 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationSecurity.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.config.xml; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +@Tag("functional") +@Tag("security") +class XmlConfigurationSecurity { + + @Test + @Timeout(5) + void xmlSecurity() { + 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..81d36e00d41 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.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.config.xml; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +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.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class XmlLoggerPropsTest { + + @BeforeAll + static void setupClass() { + System.setProperty("test", "test"); + } + + @AfterAll + static void tearDownClass() { + System.clearProperty("test"); + } + + @Test + @LoggerContextSource("log4j-loggerprops.xml") + 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/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java new file mode 100644 index 00000000000..54a4a23a15f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.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.context.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.apache.logging.log4j.test.spi.ThreadContextMapSuite; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; + +class GarbageFreeSortedArrayThreadContextMapTest extends ThreadContextMapSuite { + + private GarbageFreeSortedArrayThreadContextMap createThreadContextMap() { + return new GarbageFreeSortedArrayThreadContextMap(); + } + + private ThreadContextMap createInheritableThreadContextMap() { + final Properties props = new Properties(); + props.setProperty("log4j2.isThreadContextMapInheritable", "true"); + final PropertiesUtil util = new PropertiesUtil(props); + return new GarbageFreeSortedArrayThreadContextMap(util); + } + + @Test + void singleValue() { + singleValue(createThreadContextMap()); + } + + @Test + void testPutAll() { + final GarbageFreeSortedArrayThreadContextMap map = createThreadContextMap(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + final int mapSize = 10; + final Map newMap = new HashMap<>(mapSize); + for (int i = 1; i <= mapSize; i++) { + newMap.put("key" + i, "value" + i); + } + map.putAll(newMap); + assertFalse(map.isEmpty()); + for (int i = 1; i <= mapSize; i++) { + assertTrue(map.containsKey("key" + i)); + assertEquals("value" + i, map.get("key" + i)); + } + } + + @Test + void testClear() { + final GarbageFreeSortedArrayThreadContextMap map = createMap(); + + map.clear(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + assertFalse(map.containsKey("key2")); + } + + private GarbageFreeSortedArrayThreadContextMap createMap() { + final GarbageFreeSortedArrayThreadContextMap map = createThreadContextMap(); + assertTrue(map.isEmpty()); + map.put("key", "value"); + map.put("key2", "value2"); + assertEquals("value", map.get("key")); + assertEquals("value2", map.get("key2")); + return map; + } + + @Test + void getCopyReturnsMutableCopy() { + getCopyReturnsMutableCopy(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsNullIfEmpty() { + getImmutableMapReturnsNullIfEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsImmutableMapIfNonEmpty() { + getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapCopyNotAffectedByContextMapChanges() { + getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap()); + } + + @Test + void threadLocalNotInheritableByDefault() { + threadLocalNotInheritableByDefault(createThreadContextMap()); + } + + @Test + void threadLocalInheritableIfConfigured() { + threadLocalInheritableIfConfigured(createInheritableThreadContextMap()); + } +} 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 75% 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..8996b9f2f16 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 @@ -1,20 +1,22 @@ -package org.apache.logging.log4j.core.filter; /* * 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.logging.log4j.core.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -22,17 +24,15 @@ 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 static org.junit.Assert.*; +import org.junit.jupiter.api.Test; /** * Tests the AbstractFilter test. */ -public class AbstractFilterTest { +class AbstractFilterTest { @Test - public void testUnrolledBackwardsCompatible() { + void testUnrolledBackwardsCompatible() { final ConcreteFilter filter = new ConcreteFilter(); final Filter.Result expected = Filter.Result.DENY; verifyMethodsWithUnrolledVarargs(filter, Filter.Result.DENY); @@ -62,27 +62,28 @@ private void verifyMethodsWithUnrolledVarargs(final ConcreteFilter filter, final */ static class ConcreteFilter extends AbstractFilter { Result testResult = Result.DENY; + @Override public Result filter(final LogEvent event) { return testResult; } @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, - final Throwable t) { + public Result filter( + final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { return testResult; } @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, - final Throwable t) { + public Result filter( + final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { return testResult; } @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, - final Object... params) { + public Result filter( + final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { return testResult; } } -} \ No newline at end of file +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..4f117a1ccf5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AbstractFilterableTest { + + MockedAbstractFilterable filterable; + + @BeforeEach + void setup() { + filterable = new MockedAbstractFilterable(); + } + + @Test + void testAddSimpleFilter() { + final Filter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + + filterable.addFilter(filter); + assertSame(filter, filterable.getFilter()); + } + + @Test + void testAddMultipleSimpleFilters() { + final Filter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + + filterable.addFilter(filter); + assertSame(filter, filterable.getFilter()); + // adding a second filter converts the filter + // into a CompositeFilter.class + filterable.addFilter(filter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testAddMultipleEqualSimpleFilter() { + final Filter filter = new EqualFilter("test"); + + filterable.addFilter(filter); + assertSame(filter, filterable.getFilter()); + // adding a second filter converts the filter + // into a CompositeFilter.class + filterable.addFilter(filter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testAddCompositeFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + + filterable.addFilter(compositeFilter); + assertSame(compositeFilter, filterable.getFilter()); + } + + @Test + void testAddMultipleCompositeFilters() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter3 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2, filter3); + + filterable.addFilter(compositeFilter); + assertSame(compositeFilter, filterable.getFilter()); + // adding a second filter converts the filter + // into a CompositeFilter.class + filterable.addFilter(compositeFilter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testAddSimpleFilterAndCompositeFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter notInCompositeFilterFilter = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + + filterable.addFilter(notInCompositeFilterFilter); + assertSame(notInCompositeFilterFilter, filterable.getFilter()); + // adding a second filter converts the filter + // into a CompositeFilter.class + filterable.addFilter(compositeFilter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testAddCompositeFilterAndSimpleFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter notInCompositeFilterFilter = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + + filterable.addFilter(compositeFilter); + assertSame(compositeFilter, filterable.getFilter()); + // adding a second filter converts the filter + // into a CompositeFilter.class + filterable.addFilter(notInCompositeFilterFilter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testRemoveSimpleFilterFromSimpleFilter() { + final Filter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + + filterable.addFilter(filter); + filterable.removeFilter(filter); + assertNull(filterable.getFilter()); + } + + @Test + void testRemoveSimpleEqualFilterFromSimpleFilter() { + final Filter filterOriginal = new EqualFilter("test"); + final Filter filterCopy = new EqualFilter("test"); + + filterable.addFilter(filterOriginal); + filterable.removeFilter(filterCopy); + assertNull(filterable.getFilter()); + } + + @Test + void testRemoveSimpleEqualFilterFromTwoSimpleFilters() { + final Filter filterOriginal = new EqualFilter("test"); + final Filter filterCopy = new EqualFilter("test"); + + filterable.addFilter(filterOriginal); + filterable.addFilter(filterOriginal); + filterable.removeFilter(filterCopy); + assertSame(filterOriginal, filterable.getFilter()); + filterable.removeFilter(filterCopy); + assertNull(filterable.getFilter()); + } + + @Test + void testRemoveSimpleEqualFilterFromMultipleSimpleFilters() { + final Filter filterOriginal = new EqualFilter("test"); + final Filter filterCopy = new EqualFilter("test"); + + filterable.addFilter(filterOriginal); + filterable.addFilter(filterOriginal); + filterable.addFilter(filterCopy); + filterable.removeFilter(filterCopy); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + filterable.removeFilter(filterCopy); + assertEquals(filterOriginal, filterable.getFilter()); + filterable.removeFilter(filterOriginal); + assertNull(filterable.getFilter()); + } + + @Test + void testRemoveNullFromSingleSimpleFilter() { + final Filter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + + filterable.addFilter(filter); + filterable.removeFilter(null); + assertSame(filter, filterable.getFilter()); + } + + @Test + void testRemoveNonExistingFilterFromSingleSimpleFilter() { + final Filter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter newFilter = ThresholdFilter.createFilter(Level.WARN, null, null); + + filterable.addFilter(filter); + filterable.removeFilter(newFilter); + assertSame(filter, filterable.getFilter()); + } + + @Test + void testRemoveSimpleFilterFromCompositeFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + + filterable.addFilter(compositeFilter); + + // should remove internal filter of compositeFilter + filterable.removeFilter(filter1); + assertFalse(filterable.getFilter() instanceof CompositeFilter); + + assertEquals(filter2, filterable.getFilter()); + } + + @Test + void testRemoveSimpleFilterFromCompositeAndSimpleFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + final Filter anotherFilter = ThresholdFilter.createFilter(Level.WARN, null, null); + + filterable.addFilter(compositeFilter); + filterable.addFilter(anotherFilter); + + // should not remove internal filter of compositeFilter + filterable.removeFilter(anotherFilter); + assertInstanceOf(CompositeFilter.class, filterable.getFilter()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + } + + @Test + void testRemoveCompositeFilterFromCompositeFilter() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + + filterable.addFilter(compositeFilter); + filterable.removeFilter(compositeFilter); + assertNull(filterable.getFilter()); + } + + @Test + void testRemoveFiltersFromComposite() { + final Filter filter1 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter filter2 = ThresholdFilter.createFilter(Level.ERROR, null, null); + final Filter compositeFilter = CompositeFilter.createFilters(filter1, filter2); + final Filter anotherFilter = ThresholdFilter.createFilter(Level.WARN, null, null); + + filterable.addFilter(compositeFilter); + filterable.addFilter(anotherFilter); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + filterable.removeFilter(filter1); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + filterable.removeFilter(filter2); + assertSame(anotherFilter, filterable.getFilter()); + } + + private static class MockedAbstractFilterable extends AbstractFilterable {} + + private static class EqualFilter extends AbstractFilter { + private final String key; + + public EqualFilter(final String key) { + this.key = key; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EqualFilter)) { + return false; + } + + final EqualFilter that = (EqualFilter) o; + + if (key != null ? !key.equals(that.key) : that.key != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return key != null ? key.hashCode() : 0; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.java new file mode 100644 index 00000000000..a9fcb1fc0ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractScriptFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +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.Named; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@UsingThreadContextMap +@Tag("groovy") +public abstract class AbstractScriptFilterTest { + + @Test + public void testGroovyFilter(final LoggerContext context, @Named("List") final ListAppender app) { + final Logger logger = context.getLogger("TestGroovyFilter"); + logger.traceEntry(); + logger.info("This should not be logged"); + ThreadContext.put("UserId", "JohnDoe"); + logger.info("This should be logged"); + ThreadContext.clearMap(); + try { + final List messages = app.getMessages(); + assertNotNull(messages, "No Messages"); + assertEquals(2, messages.size(), "Incorrect number of messages. Expected 2, Actual " + messages.size()); + } finally { + app.clear(); + } + } + + @Test + public void testJavascriptFilter(final LoggerContext context, @Named("List") final ListAppender app) { + final Logger logger = context.getLogger("TestJavaScriptFilter"); + logger.traceEntry(); + logger.info("This should not be logged"); + ThreadContext.put("UserId", "JohnDoe"); + logger.info("This should be logged"); + ThreadContext.clearMap(); + final List messages = app.getMessages(); + try { + assertNotNull(messages, "No Messages"); + assertEquals(2, messages.size(), "Incorrect number of messages. Expected 2, Actual " + messages.size()); + } finally { + app.clear(); + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..8fd101038eb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.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.filter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; + +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** + * Unit test for BurstFilter. + */ +class BurstFilterLogDelayTest { + + @Test + void testCompareToOverflow() { + // no overflow, but close + final Delayed d1 = + BurstFilter.createLogDelay(Long.MAX_VALUE - TimeUnit.SECONDS.toNanos(10) - System.nanoTime()); + + // Overflow + final Delayed d2 = + BurstFilter.createLogDelay(Long.MAX_VALUE + TimeUnit.SECONDS.toNanos(10) - System.nanoTime()); + + assertThat(d2, is(greaterThan(d1))); + } +} 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..4d03e838b9e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.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.filter; + +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 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.Test; + +/** + * Unit test for BurstFilter. + */ +@LoggerContextSource("log4j-burst.xml") +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 + 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..71b2d99c992 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.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.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; + +class CompositeFilterTest { + + @Test + 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(a); + final CompositeFilter singleB = CompositeFilter.createFilters(b); + final CompositeFilter singleC = CompositeFilter.createFilters(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..2bbaa0c5551 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +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; + +@UsingThreadContextMap +class DynamicThresholdFilterTest { + + @Test + 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.createFilter("userid", pairs, Level.ERROR, null, null); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, 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 + 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.createFilter( + "userid", pairs, Level.ERROR, Filter.Result.ACCEPT, Filter.Result.NEUTRAL); + 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") + void testConfig(final Configuration config) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No DynamicThresholdFilter"); + assertInstanceOf(DynamicThresholdFilter.class, filter, "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/LevelRangeFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java new file mode 100644 index 00000000000..2041de62d67 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.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.filter; + +import static org.apache.logging.log4j.core.filter.LevelRangeFilter.createFilter; +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class LevelRangeFilterTest { + + @Test + void verify_constants() { + assertThat(LevelRangeFilter.DEFAULT_MIN_LEVEL).isEqualTo(Level.OFF); + assertThat(LevelRangeFilter.DEFAULT_MAX_LEVEL).isEqualTo(Level.ALL); + assertThat(LevelRangeFilter.DEFAULT_ON_MATCH).isEqualTo(Result.NEUTRAL); + assertThat(LevelRangeFilter.DEFAULT_ON_MISMATCH).isEqualTo(Result.DENY); + } + + @Test + void verify_defaults() { + final LevelRangeFilter filter = createFilter(null, null, null, null); + assertThat(filter.getMinLevel()).isEqualTo(Level.OFF); + assertThat(filter.getMaxLevel()).isEqualTo(Level.ALL); + assertThat(filter.getOnMatch()).isEqualTo(Result.NEUTRAL); + assertThat(filter.getOnMismatch()).isEqualTo(Result.DENY); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.Level#values") + void default_should_match_all_levels(final Level level) { + final LevelRangeFilter filter = createFilter(null, null, null, null); + assertThat(filter.filter(createEvent(level))).isEqualTo(LevelRangeFilter.DEFAULT_ON_MATCH); + } + + @Test + void overriding_defaults_should_be_effective() { + + // Choose a configuration + final Level minLevel = Level.ERROR; + final Level maxLevel = Level.WARN; + final Result onMatch = Result.ACCEPT; + final Result onMismatch = Result.NEUTRAL; + + // Verify we deviate from the defaults + assertThat(minLevel).isNotEqualTo(LevelRangeFilter.DEFAULT_MIN_LEVEL); + assertThat(maxLevel).isNotEqualTo(LevelRangeFilter.DEFAULT_MAX_LEVEL); + assertThat(onMatch).isNotEqualTo(LevelRangeFilter.DEFAULT_ON_MATCH); + assertThat(onMismatch).isNotEqualTo(LevelRangeFilter.DEFAULT_ON_MISMATCH); + + // Verify the filtering + final LevelRangeFilter filter = createFilter(minLevel, maxLevel, onMatch, onMismatch); + final SoftAssertions assertions = new SoftAssertions(); + for (final Level level : Level.values()) { + final Result expectedResult = level.isInRange(minLevel, maxLevel) ? onMatch : onMismatch; + assertions.assertThat(filter.filter(createEvent(level))).isEqualTo(expectedResult); + } + assertions.assertAll(); + } + + private static LogEvent createEvent(final Level level) { + final SimpleMessage message = new SimpleMessage("test message at level " + level); + return Log4jLogEvent.newBuilder().setLevel(level).setMessage(message).build(); + } +} 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..49a4bf9066f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.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.util.KeyValuePair; +import org.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; +import org.junit.jupiter.api.Test; + +class MapFilterTest { + + @Test + 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") + void testConfig(final Configuration config, @Named("LIST") final ListAppender app) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No MapFilter"); + assertInstanceOf(MapFilter.class, filter, "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-test/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 new file mode 100644 index 00000000000..82812e4da98 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +class MarkerFilterTest { + + @Test + void testMarkers() { + final Marker parent = MarkerManager.getMarker("Parent"); + final Marker child = MarkerManager.getMarker("Child").setParents(parent); + final Marker grandChild = MarkerManager.getMarker("GrandChild").setParents(child); + final Marker sibling = MarkerManager.getMarker("Sibling").setParents(parent); + final Marker stranger = MarkerManager.getMarker("Stranger"); + MarkerFilter filter = MarkerFilter.createFilter("Parent", null, null); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.DENY, filter.filter(null, null, stranger, (Object) null, null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, null, child, (Object) null, null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, null, grandChild, (Object) null, null)); + filter.stop(); + LogEvent event = Log4jLogEvent.newBuilder() // + .setMarker(grandChild) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + filter = MarkerFilter.createFilter("Child", null, null); + filter.start(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + event = Log4jLogEvent.newBuilder() // + .setMarker(sibling) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertSame(Filter.Result.DENY, filter.filter(event)); + filter.stop(); + } +} 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..a2d3919d1dd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.logging.log4j.core.net.WireMockUtil.createMapping; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +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.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingTestProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Unit test for simple App. + */ +@SetTestProperty(key = "log4j2.configurationAllowedProtocols", value = "http,https") +@SetTestProperty(key = "log4j2.configurationPassword", value = "log4j") +@SetTestProperty(key = "log4j2.configurationUsername", value = "log4j") +@UsingTestProperties +@WireMockTest +class MutableThreadContextMapFilterTest implements MutableThreadContextMapFilter.FilterConfigUpdateListener { + + private static final BasicCredentials CREDENTIALS = new BasicCredentials("log4j", "log4j"); + private static final String FILE_NAME = "testConfig.json"; + private static final String URL_PATH = "/" + FILE_NAME; + private static final String JSON = "application/json"; + + private static final byte[] EMPTY_CONFIG = ("{" // + + " \"configs\":{}" // + + "}") + .getBytes(UTF_8); + private static final byte[] FILTER_CONFIG = ("{" // + + " \"configs\": {" // + + " \"loginId\": [\"rgoers\", \"adam\"]," // + + " \"corpAcctNumber\": [\"30510263\"]" // + + " }" // + + "}") + .getBytes(UTF_8); + + private static final String CONFIG = "filter/MutableThreadContextMapFilterTest.xml"; + private static LoggerContext loggerContext = null; + private final ReentrantLock lock = new ReentrantLock(); + private final Condition filterUpdated = lock.newCondition(); + private final Condition resultVerified = lock.newCondition(); + private Exception exception; + + @AfterEach + void cleanup() { + exception = null; + ThreadContext.clearMap(); + if (loggerContext != null) { + loggerContext.stop(); + loggerContext = null; + } + } + + @Test + void file_location_works(TestProperties properties, @TempDir Path dir) throws Exception { + // Set up the test file. + Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS); + Instant before = now.minus(1, ChronoUnit.MINUTES); + Instant after = now.plus(1, ChronoUnit.MINUTES); + Path testConfig = dir.resolve("testConfig.json"); + properties.setProperty("configLocation", testConfig.toString()); + try (final InputStream inputStream = new ByteArrayInputStream(EMPTY_CONFIG)) { + Files.copy(inputStream, testConfig); + Files.setLastModifiedTime(testConfig, FileTime.from(before)); + } + // Setup Log4j + ConfigurationSource source = + ConfigurationSource.fromResource(CONFIG, getClass().getClassLoader()); + Configuration configuration = ConfigurationFactory.getInstance().getConfiguration(null, source); + configuration.initialize(); // To create the components + final ListAppender app = configuration.getAppender("LIST"); + assertThat(app).isNotNull(); + final MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) configuration.getFilter(); + assertNotNull(filter); + filter.registerListener(this); + + lock.lock(); + try { + // Starts the configuration + loggerContext = Configurator.initialize(getClass().getClassLoader(), configuration); + assertNotNull(loggerContext); + + final Logger logger = loggerContext.getLogger(MutableThreadContextMapFilterTest.class); + + assertThat(filterUpdated.await(20, TimeUnit.SECONDS)) + .as("Initial configuration was loaded") + .isTrue(); + ThreadContext.put("loginId", "rgoers"); + logger.debug("This is a test"); + assertThat(app.getEvents()).isEmpty(); + + // Prepare the second test case: updated config + try (final InputStream inputStream = new ByteArrayInputStream(FILTER_CONFIG)) { + Files.copy(inputStream, testConfig, StandardCopyOption.REPLACE_EXISTING); + Files.setLastModifiedTime(testConfig, FileTime.from(after)); + } + resultVerified.signalAll(); + + assertThat(filterUpdated.await(20, TimeUnit.SECONDS)) + .as("Updated configuration was loaded") + .isTrue(); + logger.debug("This is a test"); + assertThat(app.getEvents()).hasSize(1); + + // Prepare the third test case: removed config + Files.delete(testConfig); + resultVerified.signalAll(); + + assertThat(filterUpdated.await(20, TimeUnit.SECONDS)) + .as("Configuration removal was detected") + .isTrue(); + logger.debug("This is a test"); + assertThat(app.getEvents()).hasSize(1); + resultVerified.signalAll(); + } finally { + lock.unlock(); + } + assertThat(exception).as("Asynchronous exception").isNull(); + } + + @Test + void http_location_works(TestProperties properties, WireMockRuntimeInfo info) throws Exception { + WireMock wireMock = info.getWireMock(); + // Setup WireMock + // The HTTP Last-Modified header has a precision of 1 second + ZonedDateTime now = LocalDateTime.now().atZone(ZoneOffset.UTC); + ZonedDateTime before = now.minusMinutes(1); + ZonedDateTime after = now.plusMinutes(1); + properties.setProperty("configLocation", info.getHttpBaseUrl() + URL_PATH); + // Setup Log4j + ConfigurationSource source = + ConfigurationSource.fromResource(CONFIG, getClass().getClassLoader()); + Configuration configuration = ConfigurationFactory.getInstance().getConfiguration(null, source); + configuration.initialize(); // To create the components + final ListAppender app = configuration.getAppender("LIST"); + assertThat(app).isNotNull(); + final MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) configuration.getFilter(); + assertNotNull(filter); + filter.registerListener(this); + lock.lock(); + try { + // Prepare the first test case: original empty config + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, EMPTY_CONFIG, JSON, before)); + // Starts the configuration + loggerContext = Configurator.initialize(getClass().getClassLoader(), configuration); + assertNotNull(loggerContext); + + final Logger logger = loggerContext.getLogger(MutableThreadContextMapFilterTest.class); + + assertThat(filterUpdated.await(2, TimeUnit.SECONDS)) + .as("Initial configuration was loaded") + .isTrue(); + ThreadContext.put("loginId", "rgoers"); + logger.debug("This is a test"); + assertThat(app.getEvents()).isEmpty(); + + // Prepare the second test case: updated config + wireMock.removeMappings(); + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, FILTER_CONFIG, JSON, after)); + resultVerified.signalAll(); + + assertThat(filterUpdated.await(2, TimeUnit.SECONDS)) + .as("Updated configuration was loaded") + .isTrue(); + logger.debug("This is a test"); + assertThat(app.getEvents()).hasSize(1); + + // Prepare the third test case: removed config + wireMock.removeMappings(); + resultVerified.signalAll(); + + assertThat(filterUpdated.await(2, TimeUnit.SECONDS)) + .as("Configuration removal was detected") + .isTrue(); + logger.debug("This is a test"); + assertThat(app.getEvents()).hasSize(1); + resultVerified.signalAll(); + } finally { + lock.unlock(); + } + assertThat(exception).as("Asynchronous exception").isNull(); + } + + @Override + public void onEvent() { + lock.lock(); + try { + filterUpdated.signalAll(); + resultVerified.await(); + } catch (final InterruptedException e) { + exception = e; + } finally { + lock.unlock(); + } + } +} 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..cf51067dee4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +class NoMarkerFilterTest { + + @Test + void testMarkers() { + final Marker sampleMarker = MarkerManager.getMarker("SampleMarker"); + final NoMarkerFilter filter = NoMarkerFilter.newBuilder().build(); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.DENY, filter.filter(null, null, sampleMarker, (Object) null, null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, null, null, (Object) null, 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-test/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 new file mode 100644 index 00000000000..671d998258b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.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.filter; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +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 org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class RegexFilterTest { + @BeforeAll + static void before() { + StatusLogger.getLogger().setLevel(Level.OFF); + } + + @Test + void testRegexFilterDoesNotThrowWithAllTheParametersExceptRegexEqualNull() { + assertDoesNotThrow(() -> { + RegexFilter.createFilter(".* test .*", null, null, null, null); + }); + } + + @Test + void testThresholds() throws Exception { + RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + filter.start(); + assertTrue(filter.isStarted()); + assertSame( + Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, (Object) "This is a test message", null)); + assertSame(Filter.Result.DENY, filter.filter(null, Level.ERROR, null, (Object) "This is not a test", null)); + LogEvent event = Log4jLogEvent.newBuilder() // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Another test message")) // + .build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + event = Log4jLogEvent.newBuilder() // + .setLevel(Level.ERROR) // + .setMessage(new SimpleMessage("test")) // + .build(); + assertSame(Filter.Result.DENY, filter.filter(event)); + filter = RegexFilter.createFilter(null, null, false, null, null); + assertNull(filter); + } + + @Test + void testDotAllPattern() throws Exception { + final String singleLine = "test single line matches"; + final String multiLine = "test multi line matches\nsome more lines"; + final RegexFilter filter = RegexFilter.createFilter( + ".*line.*", new String[] {"DOTALL", "COMMENTS"}, false, Filter.Result.DENY, Filter.Result.ACCEPT); + final Result singleLineResult = filter.filter(null, null, null, (Object) singleLine, null); + final Result multiLineResult = filter.filter(null, null, null, (Object) multiLine, null); + assertThat(singleLineResult, equalTo(Result.DENY)); + assertThat(multiLineResult, equalTo(Result.DENY)); + } + + @Test + void testNoMsg() throws Exception { + final RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Message) null, null)); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, null, (Object[]) null)); + } + + @Test + 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/ScriptFileFilterPropertiesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterPropertiesTest.java new file mode 100644 index 00000000000..80ac2975b32 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterPropertiesTest.java @@ -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. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Constants; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "Groovy, Javascript") +@LoggerContextSource("log4j-scriptFile-filters.properties") +public class ScriptFileFilterPropertiesTest extends AbstractScriptFilterTest {} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java new file mode 100644 index 00000000000..a596620bec9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFileFilterTest.java @@ -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. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Constants; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "Groovy, Javascript") +@LoggerContextSource("log4j-scriptFile-filters.xml") +public class ScriptFileFilterTest extends AbstractScriptFilterTest {} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java new file mode 100644 index 00000000000..1cc4516a2e5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptFilterTest.java @@ -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. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Constants; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "Groovy, Javascript") +@LoggerContextSource("log4j-script-filters.xml") +public class ScriptFilterTest extends AbstractScriptFilterTest {} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java new file mode 100644 index 00000000000..f53910dca35 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ScriptRefFilterTest.java @@ -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. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Constants; +import org.junitpioneer.jupiter.SetSystemProperty; + +@SetSystemProperty(key = Constants.SCRIPT_LANGUAGES, value = "Groovy, Javascript") +@LoggerContextSource("log4j-scriptRef-filters.xml") +public class ScriptRefFilterTest extends AbstractScriptFilterTest {} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.java new file mode 100644 index 00000000000..c95f607347f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StringMatchFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +/** + * Unit test for {@link StringMatchFilter}. + */ +class StringMatchFilterTest { + + /** + * Test that if no match-string is set on the builder, the '{@link StringMatchFilter.Builder#build()}' returns + * {@code null}. + */ + @Test + void testFilterBuilderFailsWithNullText() { + assertNull(StringMatchFilter.newBuilder().build()); + } + + /** + * Test that if a {@code null} string is set as a match-pattern, an {@code IllegalArgumentExeption} is thrown. + */ + @Test + void testFilterBuilderFailsWithExceptionOnNullText() { + assertThrows(IllegalArgumentException.class, () -> StringMatchFilter.newBuilder() + .setText(null)); + } + + /** + * Test that if an empty ({@code ""}) string is set as a match-pattern, an {@code IllegalArgumentException} is thrown. + */ + @Test + void testFilterBuilderFailsWithExceptionOnEmptyText() { + assertThrows(IllegalArgumentException.class, () -> StringMatchFilter.newBuilder() + .setText("")); + } + + /** + * Test that if a {@link StringMatchFilter} is specified with a 'text' attribute it is correctly instantiated. + * + * @param configuration the configuration + */ + @Test + @LoggerContextSource("log4j2-stringmatchfilter-3153-ok.xml") + void testConfigurationWithTextPOS(final Configuration configuration) { + final Filter filter = configuration.getFilter(); + assertNotNull(filter, "The filter should not be null."); + assertInstanceOf( + StringMatchFilter.class, filter, "Expected a StringMatchFilter, but got: " + filter.getClass()); + assertEquals("FooBar", filter.toString()); + } + + /** + * Test that if a {@link StringMatchFilter} is specified without a 'text' attribute it is not instantiated. + * + * @param configuration the configuration + */ + @Test + @LoggerContextSource("log4j2-stringmatchfilter-3153-nok.xml") + void testConfigurationWithTextNEG(final Configuration configuration) { + final Filter filter = configuration.getFilter(); + assertNull(filter, "The filter should be null."); + } +} 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..5e33aaf8b36 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; +import org.junit.jupiter.api.Test; + +class StructuredDataFilterTest { + + @Test + 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") + void testConfig(final Configuration config) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No StructuredDataFilter"); + assertInstanceOf(StructuredDataFilter.class, filter, "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-test/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 new file mode 100644 index 00000000000..fed69b81d47 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.util.KeyValuePair; +import org.junit.jupiter.api.Test; + +class ThreadContextMapFilterTest { + + @Test + void testFilter() { + ThreadContext.put("userid", "testuser"); + ThreadContext.put("organization", "Apache"); + 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, null)); + ThreadContext.remove("userid"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + ThreadContext.put("userid", "JohnDoe"); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, null)); + ThreadContext.put("organization", "ASF"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + ThreadContext.clearMap(); + filter = ThreadContextMapFilter.createFilter(pairs, "or", null, null); + assertNotNull(filter); + filter.start(); + assertTrue(filter.isStarted()); + ThreadContext.put("userid", "testuser"); + ThreadContext.put("organization", "Apache"); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + ThreadContext.put("organization", "ASF"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + ThreadContext.remove("organization"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, 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, null)); + ThreadContext.clearMap(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..0b956bc1a59 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.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.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.Level; +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; + +class ThresholdFilterTest { + + @Test + void testThresholds() { + final ThresholdFilter filter = ThresholdFilter.createFilter(Level.ERROR, null, null); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, null)); + 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)); + } +} 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..643d69355c9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.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.filter; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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 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.util.Clock; +import org.apache.logging.log4j.core.util.ClockFactory; +import org.apache.logging.log4j.core.util.ClockFactoryTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class TimeFilterTest { + private static long CLOCKTIME = System.currentTimeMillis(); + + /** Helper class */ + public static class FixedTimeClock implements Clock { + @Override + public long currentTimeMillis() { + return CLOCKTIME; + } + } + + @BeforeAll + static void beforeClass() { + System.setProperty(ClockFactory.PROPERTY_NAME, FixedTimeClock.class.getName()); + } + + @AfterAll + static void afterClass() throws IllegalAccessException { + ClockFactoryTest.resetClocks(); + } + + @Test + 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)); + 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); + 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); + 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); + } + + @Test + 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)); + 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); + 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); + 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); + 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); + } + + @Test + 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)); + 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); + 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); + 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); + 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); + } + + @Test + 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)); + 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); + 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); + 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); + 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); + } + + @Test + 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)); + 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); + 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); + 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); + 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); + } + + @Test + void testTime() { + // https://garygregory.wordpress.com/2013/06/18/what-are-the-java-timezone-ids/ + final TimeFilter filter = TimeFilter.createFilter("02:00:00", "03:00:00", "America/Los_Angeles", null, null); + 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); + + 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); + // 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); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..5ed076bd85a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.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.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.lang.reflect.Field; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.junit.jupiter.api.Test; + +/** + * Tests the ContextDataFactory class. + */ +class ContextDataFactoryPropertySetMissingConstructorTest { + + @Test + void intArgReturnsSortedArrayStringMapIfPropertySpecifiedButMissingIntConstructor() throws Exception { + System.setProperty("log4j2.ContextData", FactoryTestStringMapWithoutIntConstructor.class.getName()); + assertInstanceOf(SortedArrayStringMap.class, ContextDataFactory.createContextData(2)); + 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"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..03d8f46dae0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.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.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +/** + * Tests the ContextDataFactory class. + */ +class ContextDataFactoryPropertySetTest { + + @Test + void noArgReturnsSpecifiedImplIfPropertySpecified() { + System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); + assertInstanceOf(FactoryTestStringMap.class, ContextDataFactory.createContextData()); + System.clearProperty("log4j2.ContextData"); + } + + @Test + void intArgReturnsSpecifiedImplIfPropertySpecified() { + System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); + assertInstanceOf(FactoryTestStringMap.class, ContextDataFactory.createContextData(2)); + System.clearProperty("log4j2.ContextData"); + } + + @Test + void intArgSetsCapacityIfPropertySpecified() { + System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); + final FactoryTestStringMap actual = (FactoryTestStringMap) ContextDataFactory.createContextData(2); + assertEquals(2, actual.initialCapacity); + System.clearProperty("log4j2.ContextData"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..c14ba5070ec --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.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.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.lang.reflect.Field; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.junit.jupiter.api.Test; + +/** + * Tests the ContextDataFactory class. + */ +class ContextDataFactoryTest { + @Test + void noArgReturnsSortedArrayStringMapIfNoPropertySpecified() { + assertInstanceOf(SortedArrayStringMap.class, ContextDataFactory.createContextData()); + } + + @Test + void intArgReturnsSortedArrayStringMapIfNoPropertySpecified() { + assertInstanceOf(SortedArrayStringMap.class, ContextDataFactory.createContextData(2)); + } + + @Test + 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)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..8a9ec1bfa01 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.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; + +import java.util.Map; +import org.apache.logging.log4j.util.BiConsumer; +import org.apache.logging.log4j.util.IndexedStringMap; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.TriConsumer; + +/** + * Dummy implementation of the StringMap interface for testing. + */ +public class FactoryTestStringMap implements IndexedStringMap { + private static final long serialVersionUID = -2035823164390218862L; + int initialCapacity; + + public FactoryTestStringMap() {} + + public FactoryTestStringMap(final int initialCapacity) { + this.initialCapacity = initialCapacity; + } + + @Override + public void clear() { + // do nothing + } + + @Override + public boolean containsKey(final String key) { + return false; + } + + @Override + public void forEach(final BiConsumer action) { + // do nothing + } + + @Override + public void forEach(final TriConsumer action, final S state) { + // do nothing + } + + @Override + public void freeze() { + // do nothing + } + + @Override + public String getKeyAt(final int index) { + return null; + } + + @Override + public V getValue(final String key) { + return null; + } + + @Override + public V getValueAt(final int index) { + return null; + } + + @Override + public int indexOfKey(final String key) { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean isFrozen() { + return false; + } + + @Override + public void putAll(final ReadOnlyStringMap source) { + // do nothing + } + + @Override + public void putValue(final String key, final Object value) { + // do nothing + } + + @Override + public void remove(final String key) { + // do nothing + } + + @Override + public int size() { + return 0; + } + + @Override + public Map toMap() { + return null; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ReadOnlyStringMap)) { + return false; + } + if (size() != ((ReadOnlyStringMap) obj).size()) { + return false; + } + + // Convert to maps and compare + final Map thisMap = toMap(); + final Map otherMap = ((ReadOnlyStringMap) obj).toMap(); + return thisMap.equals(otherMap); + } + + @Override + public int hashCode() { + return toMap().hashCode(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..74947ba75fb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.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.impl; + +import java.util.Map; +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.TriConsumer; + +/** + * Dummy implementation of the StringMap interface for testing. + */ +public class FactoryTestStringMapWithoutIntConstructor implements StringMap { + private static final long serialVersionUID = -3239395494628445052L; + int initialCapacity; + + public FactoryTestStringMapWithoutIntConstructor() {} + + @Override + public Map toMap() { + return null; + } + + @Override + public boolean containsKey(final String key) { + return false; + } + + @Override + public void forEach(final BiConsumer action) {} + + @Override + public void forEach(final TriConsumer action, final S state) {} + + @Override + public V getValue(final String key) { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public void clear() {} + + @Override + public void freeze() {} + + @Override + public boolean isFrozen() { + return false; + } + + @Override + public void putAll(final ReadOnlyStringMap source) {} + + @Override + public void putValue(final String key, final Object value) {} + + @Override + public void remove(final String key) {} + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ReadOnlyStringMap)) { + return false; + } + if (size() != ((ReadOnlyStringMap) obj).size()) { + return false; + } + + // Convert to maps and compare + final Map thisMap = toMap(); + final Map otherMap = ((ReadOnlyStringMap) obj).toMap(); + return thisMap.equals(otherMap); + } + + @Override + public int hashCode() { + return toMap().hashCode(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..f9c5ab3a409 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java @@ -0,0 +1,886 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.apache.logging.log4j.util.BiConsumer; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.TriConsumer; +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; + +/** + * Tests the JdkMapAdapterStringMap class. + */ +class JdkMapAdapterStringMapTest { + + @Test + void testConstructorDisallowsNull() { + assertThrows(NullPointerException.class, () -> new JdkMapAdapterStringMap(null, false)); + } + + @Test + void testToString() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("a2", "bvalue"); + original.putValue("B", "Bvalue"); + original.putValue("C", "Cvalue"); + original.putValue("3", "3value"); + assertEquals("{3=3value, B=Bvalue, C=Cvalue, a=avalue, a2=bvalue}", original.toString()); + } + + @Test + void testSerialization() throws Exception { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final byte[] binary = serialize(original); + final JdkMapAdapterStringMap copy = deserialize(binary); + assertEquals(original, copy); + } + + private byte[] serialize(final JdkMapAdapterStringMap data) throws IOException { + final ByteArrayOutputStream arr = new ByteArrayOutputStream(); + final ObjectOutputStream out = new ObjectOutputStream(arr); + out.writeObject(data); + return arr.toByteArray(); + } + + private JdkMapAdapterStringMap deserialize(final byte[] binary) throws IOException, ClassNotFoundException { + final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); + final ObjectInputStream in = new ObjectInputStream(inArr); + final JdkMapAdapterStringMap result = (JdkMapAdapterStringMap) in.readObject(); + return result; + } + + @Test + void testPutAll() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putAll(original); + assertEquals(original, other); + + other.putValue("3", "otherValue"); + assertNotEquals(original, other); + + other.putValue("3", null); + assertNotEquals(original, other); + + other.putValue("3", "3value"); + assertEquals(original, other); + } + + @Test + void testPutAll_overwritesSameKeys2() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("a", "aa"); + other.putValue("c", "cc"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cc", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + } + + @Test + void testPutAll_nullKeyInLargeOriginal() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullORIG", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInSmallOriginal() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullORIG", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInSmallAdditional() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInLargeAdditional() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInBoth_LargeOriginal() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + original.putValue("d", "dORIG"); + original.putValue("e", "eORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(7, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cORIG", original.getValue("c")); + assertEquals("dORIG", original.getValue("d")); + assertEquals("eORIG", original.getValue("e")); + assertEquals("11", original.getValue("1")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_nullKeyInBoth_SmallOriginal() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue(null, "nullORIG"); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue(null, "nullNEW"); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("3", "33"); + other.putValue("a", "aa"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + assertEquals("33", original.getValue("3")); + assertEquals("nullNEW", original.getValue(null)); + } + + @Test + void testPutAll_overwritesSameKeys1() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aORIG"); + original.putValue("b", "bORIG"); + original.putValue("c", "cORIG"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "11"); + other.putValue("2", "22"); + other.putValue("a", "aa"); + other.putValue("c", "cc"); + original.putAll(other); + + assertEquals(5, original.size(), "size after put other"); + assertEquals("aa", original.getValue("a")); + assertEquals("bORIG", original.getValue("b")); + assertEquals("cc", original.getValue("c")); + assertEquals("11", original.getValue("1")); + assertEquals("22", original.getValue("2")); + } + + @Test + void testEquals() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + assertEquals(original, original); // equal to itself + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("a", "avalue"); + assertNotEquals(original, other); + + other.putValue("B", "Bvalue"); + assertNotEquals(original, other); + + other.putValue("3", "3value"); + assertEquals(original, other); + + other.putValue("3", "otherValue"); + assertNotEquals(original, other); + + other.putValue("3", null); + assertNotEquals(original, other); + + other.putValue("3", "3value"); + assertEquals(original, other); + } + + @Test + void testToMap() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final Map expected = new HashMap<>(); + expected.put("a", "avalue"); + expected.put("B", "Bvalue"); + expected.put("3", "3value"); + + assertEquals(expected, original.toMap()); + + try { + original.toMap().put("abc", "xyz"); + } catch (final UnsupportedOperationException ex) { + fail("Expected map to be mutable, but " + ex); + } + } + + @Test + void testPutAll_KeepsExistingValues() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + assertEquals(3, original.size(), "size"); + + // add empty context data + original.putAll(new JdkMapAdapterStringMap()); + assertEquals(3, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "111"); + other.putValue("2", "222"); + other.putValue("3", "333"); + original.putAll(other); + + assertEquals(6, original.size(), "size after put other"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("111", original.getValue("1")); + assertEquals("222", original.getValue("2")); + assertEquals("333", original.getValue("3")); + } + + @Test + void testPutAll_sizePowerOfTwo() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + original.putValue("d", "ddd"); + assertEquals(4, original.size(), "size"); + + // add empty context data + original.putAll(new JdkMapAdapterStringMap()); + assertEquals(4, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + other.putValue("1", "111"); + other.putValue("2", "222"); + other.putValue("3", "333"); + other.putValue("4", "444"); + original.putAll(other); + + assertEquals(8, original.size(), "size after put other"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + assertEquals("111", original.getValue("1")); + assertEquals("222", original.getValue("2")); + assertEquals("333", original.getValue("3")); + assertEquals("444", original.getValue("4")); + } + + @Test + void testPutAll_largeAddition() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue(null, "nullVal"); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + original.putValue("d", "ddd"); + assertEquals(5, original.size(), "size"); + + final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); + for (int i = 0; i < 500; i++) { + other.putValue(String.valueOf(i), String.valueOf(i)); + } + other.putValue(null, "otherVal"); + original.putAll(other); + + assertEquals(505, original.size(), "size after put other"); + assertEquals("otherVal", original.getValue(null)); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + assertEquals("ddd", original.getValue("d")); + for (int i = 0; i < 500; i++) { + assertEquals(String.valueOf(i), original.getValue(String.valueOf(i))); + } + } + + @Test + void testPutAllSelfDoesNotModify() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "bbb"); + original.putValue("c", "ccc"); + assertEquals(3, original.size(), "size"); + + // putAll with self + original.putAll(original); + assertEquals(3, original.size(), "size after put empty"); + assertEquals("aaa", original.getValue("a")); + assertEquals("bbb", original.getValue("b")); + assertEquals("ccc", original.getValue("c")); + } + + @Test + void testNoConcurrentModificationBiConsumerPut() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.putValue("d", "aaa"); + original.putValue("e", "aaa"); + original.forEach((s, o) -> original.putValue("c" + s, "other")); + } + + @Test + void testNoConcurrentModificationBiConsumerPutValue() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.putValue("d", "aaa"); + original.putValue("e", "aaa"); + original.forEach((s, o) -> original.putValue("c" + s, "other")); + } + + @Test + void testNoConcurrentModificationBiConsumerRemove() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.forEach((s, o) -> original.remove("a")); + } + + @Test + void testNoConcurrentModificationBiConsumerClear() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.putValue("d", "aaa"); + original.putValue("e", "aaa"); + original.forEach((s, o) -> original.clear()); + } + + @Test + void testNoConcurrentModificationTriConsumerPut() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("d", "aaa"); + original.putValue("e", "aaa"); + original.forEach((s, o, o2) -> original.putValue("c", "other"), null); + } + + @Test + void testNoConcurrentModificationTriConsumerPutValue() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.putValue("d", "aaa"); + original.putValue("e", "aaa"); + original.forEach((s, o, o2) -> original.putValue("c" + s, "other"), null); + } + + @Test + void testNoConcurrentModificationTriConsumerRemove() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.forEach((s, o, o2) -> original.remove("a"), null); + } + + @Test + void testNoConcurrentModificationTriConsumerClear() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.putValue("b", "aaa"); + original.putValue("c", "aaa"); + original.putValue("d", "aaa"); + original.forEach((s, o, o2) -> original.clear(), null); + } + + @Test + void testInitiallyNotFrozen() { + assertFalse(new JdkMapAdapterStringMap().isFrozen()); + } + + @Test + void testIsFrozenAfterCallingFreeze() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + assertFalse(original.isFrozen(), "before freeze"); + original.freeze(); + assertTrue(original.isFrozen(), "after freeze"); + } + + @Test + void testFreezeProhibitsPutValue() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.freeze(); + assertThrows(UnsupportedOperationException.class, () -> original.putValue("a", "aaa")); + } + + @Test + void testFreezeProhibitsRemove() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("b", "bbb"); + original.freeze(); + assertThrows( + UnsupportedOperationException.class, + () -> original.remove("b")); // existing key: modifies the collection + } + + @Test + void testFreezeAllowsRemoveOfNonExistingKey() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("b", "bbb"); + original.freeze(); + original.remove("a"); // no actual modification + } + + @Test + void testFreezeAllowsRemoveIfEmpty() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.freeze(); + original.remove("a"); // no exception + } + + @Test + void testFreezeProhibitsClear() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "aaa"); + original.freeze(); + assertThrows(UnsupportedOperationException.class, original::clear); + } + + @Test + void testFreezeAllowsClearIfEmpty() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.freeze(); + original.clear(); + } + + @Test + void testNullKeysAllowed() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + assertEquals(5, original.size()); + + original.putValue(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals("nullvalue", original.getValue(null)); + + original.putValue(null, "otherNullvalue"); + assertEquals("otherNullvalue", original.getValue(null)); + assertEquals(6, original.size()); + + original.putValue(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals("nullvalue", original.getValue(null)); + + original.putValue(null, "abc"); + assertEquals(6, original.size()); + assertEquals("abc", original.getValue(null)); + } + + @Test + void testNullKeysCopiedToAsMap() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); + assertEquals(5, original.size()); + + final HashMap expected = new HashMap<>(); + expected.put("a", "avalue"); + expected.put("B", "Bvalue"); + expected.put("3", "3value"); + expected.put("c", "cvalue"); + expected.put("d", "dvalue"); + assertEquals(expected, original.toMap(), "initial"); + + original.putValue(null, "nullvalue"); + expected.put(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key"); + + original.putValue(null, "otherNullvalue"); + expected.put(null, "otherNullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value2"); + + original.putValue(null, "nullvalue"); + expected.put(null, "nullvalue"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value1 again"); + + original.putValue(null, "abc"); + expected.put(null, "abc"); + assertEquals(6, original.size()); + assertEquals(expected, original.toMap(), "with null key value3"); + } + + @Test + void testRemove() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertEquals("avalue", original.getValue("a")); + + original.remove("a"); + assertEquals(0, original.size()); + assertNull(original.getValue("a"), "no a val"); + + original.remove("B"); + assertEquals(0, original.size()); + assertNull(original.getValue("B"), "no B val"); + } + + @Test + void testRemoveWhenFull() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("b", "bvalue"); + original.putValue("c", "cvalue"); + original.putValue("d", "dvalue"); // default capacity = 4 + original.remove("d"); + } + + @Test + void testNullValuesArePreserved() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertEquals("avalue", original.getValue("a")); + + original.putValue("a", null); + assertEquals(1, original.size()); + assertNull(original.getValue("a"), "no a val"); + + original.putValue("B", null); + assertEquals(2, original.size()); + assertNull(original.getValue("B"), "no B val"); + } + + @Test + void testGet() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + assertEquals("avalue", original.getValue("a")); + assertEquals("Bvalue", original.getValue("B")); + assertEquals("3value", original.getValue("3")); + + original.putValue("0", "0value"); + assertEquals("0value", original.getValue("0")); + assertEquals("3value", original.getValue("3")); + assertEquals("Bvalue", original.getValue("B")); + assertEquals("avalue", original.getValue("a")); + } + + @Test + void testClear() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + assertEquals(3, original.size()); + + original.clear(); + assertEquals(0, original.size()); + } + + @Test + void testContainsKey() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + 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(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); + + original.putValue("B", "Bvalue"); + 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(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); + + original.putValue("A", "AAA"); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertTrue(original.containsKey("A"), "A"); + } + + @Test + void testSizeAndIsEmpty() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + assertEquals(0, original.size()); + assertTrue(original.isEmpty(), "initial"); + + original.putValue("a", "avalue"); + assertEquals(1, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.putValue("B", "Bvalue"); + assertEquals(2, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.putValue("3", "3value"); + assertEquals(3, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("B"); + assertEquals(2, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("3"); + assertEquals(1, original.size()); + assertFalse(original.isEmpty(), "size=" + original.size()); + + original.remove("a"); + assertEquals(0, original.size()); + assertTrue(original.isEmpty(), "size=" + original.size()); + } + + @Test + void testForEachBiConsumer() throws Exception { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + original.forEach(new BiConsumer() { + 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)); + count++; + assertTrue(count <= original.size(), "count should not exceed size but was " + count); + } + }); + } + + static class State { + JdkMapAdapterStringMap data; + int count; + } + + 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(state.count <= state.data.size(), "count should not exceed size but was " + state.count); + }; + + @Test + void testForEachTriConsumer() { + final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); + original.putValue("a", "avalue"); + original.putValue("B", "Bvalue"); + original.putValue("3", "3value"); + + final JdkMapAdapterStringMapTest.State state = new JdkMapAdapterStringMapTest.State(); + state.data = original; + original.forEach(COUNTER, state); + assertEquals(state.count, original.size()); + } + + @Test + void testEqualityWithOtherImplementations() { + final JdkMapAdapterStringMap left = new JdkMapAdapterStringMap(); + final SortedArrayStringMap right = new SortedArrayStringMap(); + assertEquals(left, right); + assertEquals(left.hashCode(), right.hashCode()); + + left.putValue("a", "avalue"); + left.putValue("B", "Bvalue"); + right.putValue("B", "Bvalue"); + right.putValue("a", "avalue"); + assertEquals(left, right); + assertEquals(left.hashCode(), right.hashCode()); + + left.remove("a"); + right.remove("a"); + assertEquals(left, right); + assertEquals(left.hashCode(), right.hashCode()); + } + + static Stream testImmutability() { + return Stream.of( + Arguments.of(new HashMap<>(), false), + Arguments.of(Collections.emptyMap(), true), + Arguments.of(Collections.unmodifiableMap(new HashMap<>()), true)); + } + + @ParameterizedTest + @MethodSource + void testImmutability(final Map map, final boolean frozen) { + assertThat(new JdkMapAdapterStringMap(map).isFrozen()).as("Frozen").isEqualTo(frozen); + } +} 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..82c091336ea --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventNanoTimeTest.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.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.core.util.SystemNanoClock; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class Log4jLogEventNanoTimeTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "NanoTimeToFileTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testLog4jLogEventUsesNanoTimeClock() throws Exception { + final File file = new File("target", "NanoTimeToFileTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final long before = System.nanoTime(); + log.info("Use actual System.nanoTime()"); + assertInstanceOf(SystemNanoClock.class, Log4jLogEvent.getNanoClock(), "using SystemNanoClock"); + + final long DUMMYNANOTIME = 123; + Log4jLogEvent.setNanoClock(new DummyNanoClock(DUMMYNANOTIME)); + log.info("Use dummy nano clock"); + assertInstanceOf(DummyNanoClock.class, Log4jLogEvent.getNanoClock(), "using SystemNanoClock"); + + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + String line1; + String line2; + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + line1 = reader.readLine(); + line2 = reader.readLine(); + // System.out.println(line1); + // System.out.println(line2); + } + file.delete(); + + 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/impl/Log4jLogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java new file mode 100644 index 00000000000..bc90e594d28 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java @@ -0,0 +1,544 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.apache.logging.log4j.test.junit.SerialUtil.deserialize; +import static org.apache.logging.log4j.test.junit.SerialUtil.serialize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +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.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.message.Message; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.message.ReusableObjectMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +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; + +public class Log4jLogEventTest { + + /** 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; + } + } + + @BeforeAll + static void beforeClass() { + System.setProperty(ClockFactory.PROPERTY_NAME, FixedTimeClock.class.getName()); + } + + @AfterAll + static void afterClass() throws IllegalAccessException { + ClockFactoryTest.resetClocks(); + } + + @Test + void testToImmutableSame() { + final LogEvent logEvent = new Log4jLogEvent(); + assertSame(logEvent, logEvent.toImmutable()); + } + + @Test + void testToImmutableNotSame() { + final LogEvent logEvent = new Log4jLogEvent.Builder() + .setMessage(new ReusableObjectMessage()) + .build(); + final LogEvent immutable = logEvent.toImmutable(); + assertSame(logEvent, immutable); + assertFalse(immutable.getMessage() instanceof ReusableMessage); + } + + @Test + void testJavaIoSerializable() { + final Log4jLogEvent evt = Log4jLogEvent.newBuilder() // + .setLoggerName("some.test") // + .setLoggerFqcn(Strings.EMPTY) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("abc")) // + .build(); + + 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.getContextMap(), evt2.getContextMap()); + assertEquals(evt.getContextData(), evt2.getContextData()); + assertEquals(evt.getContextStack(), evt2.getContextStack()); + assertEquals(evt.getMessage(), evt2.getMessage()); + assertEquals(evt.getSource(), evt2.getSource()); + assertEquals(evt.getThreadName(), evt2.getThreadName()); + assertEquals(evt.getThrown(), evt2.getThrown()); + assertEquals(evt.isEndOfBatch(), evt2.isEndOfBatch()); + assertEquals(evt.isIncludeLocation(), evt2.isIncludeLocation()); + } + + @Test + void testJavaIoSerializableWithThrown() { + final Error thrown = new InternalError("test error"); + final Log4jLogEvent evt = Log4jLogEvent.newBuilder() // + .setLoggerName("some.test") // + .setLoggerFqcn(Strings.EMPTY) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("abc")) // + .setThrown(thrown) // + .build(); + + 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.getContextMap(), evt2.getContextMap()); + assertEquals(evt.getContextData(), evt2.getContextData()); + assertEquals(evt.getContextStack(), evt2.getContextStack()); + assertEquals(evt.getMessage(), evt2.getMessage()); + 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()); + } + + // DO NOT REMOVE THIS COMMENT: + // UNCOMMENT WHEN GENERATING SERIALIZED EVENT FOR #testJavaIoSerializableWithUnknownThrowable + // public static class DeletedException extends Exception { + // private static final long serialVersionUID = 1L; + // public DeletedException(String msg) { + // super(msg); + // } + // }; + + @Test + void testJavaIoSerializableWithUnknownThrowable() { + final String loggerName = "some.test"; + final Marker marker = null; + final String loggerFQN = Strings.EMPTY; + final Level level = Level.INFO; + final Message msg = new SimpleMessage("abc"); + final String threadName = Thread.currentThread().getName(); + final String errorMessage = "OMG I've been deleted!"; + + // DO NOT DELETE THIS COMMENT: + // UNCOMMENT TO RE-GENERATE SERIALIZED EVENT WHEN UPDATING THIS TEST. + // final Exception thrown = new DeletedException(errorMessage); + // final Log4jLogEvent evt = new Log4jLogEvent(loggerName, marker, loggerFQN, level, msg, thrown); + // final byte[] binary = serialize(evt); + // String base64Str = DatatypeConverter.printBase64Binary(binary); + // System.out.println("final String base64 = \"" + base64Str.replaceAll("\r\n", "\\\\r\\\\n\" +\r\n\"") + + // "\";"); + + 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 Log4jLogEvent evt2 = deserialize(binaryDecoded); + + assertEquals(loggerFQN, evt2.getLoggerFqcn()); + assertEquals(level, evt2.getLevel()); + assertEquals(loggerName, evt2.getLoggerName()); + assertEquals(marker, evt2.getMarker()); + assertEquals(msg, evt2.getMessage()); + assertEquals(threadName, evt2.getThreadName()); + assertNull(evt2.getThrown()); + assertEquals( + this.getClass().getName() + "$DeletedException", + evt2.getThrownProxy().getName()); + assertEquals(errorMessage, evt2.getThrownProxy().getMessage()); + } + + @Test + void testNullLevelReplacedWithOFF() { + final Level NULL_LEVEL = null; + final Log4jLogEvent evt = + Log4jLogEvent.newBuilder().setLevel(NULL_LEVEL).build(); + assertEquals(Level.OFF, evt.getLevel()); + } + + @Test + void testTimestampGeneratedByClock() { + final LogEvent evt = Log4jLogEvent.newBuilder().build(); + assertEquals(FixedTimeClock.FIXED_TIME, evt.getTimeMillis()); + } + + @Test + void testInitiallyDummyNanoClock() { + assertInstanceOf(DummyNanoClock.class, Log4jLogEvent.getNanoClock()); + assertEquals(0, Log4jLogEvent.getNanoClock().nanoTime(), "initial dummy nanotime"); + } + + @Test + 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(expected, new Log4jLogEvent().getNanoTime(), "No-arg constructor"); + assertEquals(expected, new Log4jLogEvent(98).getNanoTime(), "1-arg constructor"); + assertEquals(expected, new Log4jLogEvent("l", null, "a", null, null, null).getNanoTime(), "6-arg constructor"); + assertEquals( + expected, new Log4jLogEvent("l", null, "a", null, null, null, null).getNanoTime(), "7-arg constructor"); + assertEquals( + expected, + new Log4jLogEvent("l", null, "a", null, null, null, null, null, null, null, 0).getNanoTime(), + "11-arg constructor"); + assertEquals( + expected, + Log4jLogEvent.createEvent("l", null, "a", null, null, null, null, null, null, null, null, 0) + .getNanoTime(), + "12-arg factory method"); + } + + @SuppressWarnings("deprecation") + @Test + void testBuilderCorrectlyCopiesAllEventAttributes() { + final StringMap contextData = ContextDataFactory.createContextData(); + contextData.putValue("A", "B"); + final ContextStack contextStack = ThreadContext.getImmutableStack(); + final Exception exception = new Exception("test"); + final Marker marker = MarkerManager.getMarker("EVENTTEST"); + final Message message = new SimpleMessage("foo"); + final StackTraceElement stackTraceElement = new StackTraceElement("A", "B", "file", 123); + final String fqcn = "qualified"; + final String name = "Ceci n'est pas une pipe"; + final String threadName = "threadName"; + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setContextData(contextData) // + .setContextStack(contextStack) // + .setEndOfBatch(true) // + .setIncludeLocation(true) // + .setLevel(Level.FATAL) // + .setLoggerFqcn(fqcn) // + .setLoggerName(name) // + .setMarker(marker) // + .setMessage(message) // + .setNanoTime(1234567890L) // + .setSource(stackTraceElement) // + .setThreadName(threadName) // + .setThrown(exception) // + .setTimeMillis(987654321L) + .build(); + + assertEquals(contextData, event.getContextData()); + assertSame(contextStack, event.getContextStack()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); + assertSame(Level.FATAL, event.getLevel()); + assertSame(fqcn, event.getLoggerFqcn()); + assertSame(name, event.getLoggerName()); + assertSame(marker, event.getMarker()); + assertSame(message, event.getMessage()); + assertEquals(1234567890L, event.getNanoTime()); + assertSame(stackTraceElement, event.getSource()); + assertSame(threadName, event.getThreadName()); + assertSame(exception, event.getThrown()); + assertEquals(987654321L, event.getTimeMillis()); + + final LogEvent event2 = new Log4jLogEvent.Builder(event).build(); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); + } + + @Test + void testBuilderCorrectlyCopiesAllEventAttributesInclContextData() { + final StringMap contextData = new SortedArrayStringMap(); + contextData.putValue("A", "B"); + final ContextStack contextStack = ThreadContext.getImmutableStack(); + final Exception exception = new Exception("test"); + final Marker marker = MarkerManager.getMarker("EVENTTEST"); + final Message message = new SimpleMessage("foo"); + final StackTraceElement stackTraceElement = new StackTraceElement("A", "B", "file", 123); + final String fqcn = "qualified"; + final String name = "Ceci n'est pas une pipe"; + final String threadName = "threadName"; + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setContextData(contextData) // + .setContextStack(contextStack) // + .setEndOfBatch(true) // + .setIncludeLocation(true) // + .setLevel(Level.FATAL) // + .setLoggerFqcn(fqcn) // + .setLoggerName(name) // + .setMarker(marker) // + .setMessage(message) // + .setNanoTime(1234567890L) // + .setSource(stackTraceElement) // + .setThreadName(threadName) // + .setThrown(exception) // + .setTimeMillis(987654321L) + .build(); + + assertSame(contextData, event.getContextData()); + assertSame(contextStack, event.getContextStack()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); + assertSame(Level.FATAL, event.getLevel()); + assertSame(fqcn, event.getLoggerFqcn()); + assertSame(name, event.getLoggerName()); + assertSame(marker, event.getMarker()); + assertSame(message, event.getMessage()); + assertEquals(1234567890L, event.getNanoTime()); + assertSame(stackTraceElement, event.getSource()); + assertSame(threadName, event.getThreadName()); + assertSame(exception, event.getThrown()); + assertEquals(987654321L, event.getTimeMillis()); + + final LogEvent event2 = new Log4jLogEvent.Builder(event).build(); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); + } + + @Test + void testBuilderCorrectlyCopiesMutableLogEvent() throws Exception { + final StringMap contextData = new SortedArrayStringMap(); + contextData.putValue("A", "B"); + final ContextStack contextStack = ThreadContext.getImmutableStack(); + final Exception exception = new Exception("test"); + final Marker marker = MarkerManager.getMarker("EVENTTEST"); + final Message message = new SimpleMessage("foo"); + new StackTraceElement("A", "B", "file", 123); + final String fqcn = "qualified"; + final String name = "Ceci n'est pas une pipe"; + final String threadName = "threadName"; + final MutableLogEvent event = new MutableLogEvent(); + event.setContextData(contextData); + event.setContextStack(contextStack); + event.setEndOfBatch(true); + event.setIncludeLocation(true); + // event.setSource(stackTraceElement); // cannot be explicitly set + event.setLevel(Level.FATAL); + event.setLoggerFqcn(fqcn); + event.setLoggerName(name); + event.setMarker(marker); + event.setMessage(message); + event.setNanoTime(1234567890L); + event.setThreadName(threadName); + event.setThrown(exception); + event.setTimeMillis(987654321L); + + assertSame(contextData, event.getContextData()); + assertSame(contextStack, event.getContextStack()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); + assertSame(Level.FATAL, event.getLevel()); + assertSame(fqcn, event.getLoggerFqcn()); + assertSame(name, event.getLoggerName()); + assertSame(marker, event.getMarker()); + assertSame(message, event.getMessage()); + assertEquals(1234567890L, event.getNanoTime()); + // assertSame(stackTraceElement, event.getSource()); // don't invoke + assertSame(threadName, event.getThreadName()); + assertSame(exception, event.getThrown()); + assertEquals(987654321L, event.getTimeMillis()); + + final LogEvent e2 = new Log4jLogEvent.Builder(event).build(); + assertEquals(contextData, e2.getContextData()); + assertSame(contextStack, e2.getContextStack()); + assertTrue(e2.isEndOfBatch()); + assertTrue(e2.isIncludeLocation()); + assertSame(Level.FATAL, e2.getLevel()); + assertSame(fqcn, e2.getLoggerFqcn()); + assertSame(name, e2.getLoggerName()); + assertSame(marker, e2.getMarker()); + assertSame(message, e2.getMessage()); + assertEquals(1234567890L, e2.getNanoTime()); + // assertSame(stackTraceElement, e2.getSource()); // don't invoke + assertSame(threadName, e2.getThreadName()); + assertSame(exception, e2.getThrown()); + assertEquals(987654321L, e2.getTimeMillis()); + + // use reflection to get value of source field in log event copy: + // invoking the getSource() method would initialize the field + final Field fieldSource = Log4jLogEvent.class.getDeclaredField("source"); + fieldSource.setAccessible(true); + final Object value = fieldSource.get(e2); + assertNull(value, "source in copy"); + } + + @SuppressWarnings("deprecation") + @Test + void testEquals() { + final StringMap contextData = ContextDataFactory.createContextData(); + contextData.putValue("A", "B"); + ThreadContext.push("first"); + final ContextStack contextStack = ThreadContext.getImmutableStack(); + final Exception exception = new Exception("test"); + final Marker marker = MarkerManager.getMarker("EVENTTEST"); + final Message message = new SimpleMessage("foo"); + final StackTraceElement stackTraceElement = new StackTraceElement("A", "B", "file", 123); + final String fqcn = "qualified"; + final String name = "Ceci n'est pas une pipe"; + final String threadName = "threadName"; + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setContextData(contextData) // + .setContextStack(contextStack) // + .setEndOfBatch(true) // + .setIncludeLocation(true) // + .setLevel(Level.FATAL) // + .setLoggerFqcn(fqcn) // + .setLoggerName(name) // + .setMarker(marker) // + .setMessage(message) // + .setNanoTime(1234567890L) // + .setSource(stackTraceElement) // + .setThreadName(threadName) // + .setThrown(exception) // + .setTimeMillis(987654321L) + .build(); + + assertEquals(contextData, event.getContextData()); + assertSame(contextStack, event.getContextStack()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); + assertSame(Level.FATAL, event.getLevel()); + assertSame(fqcn, event.getLoggerFqcn()); + assertSame(name, event.getLoggerName()); + assertSame(marker, event.getMarker()); + assertSame(message, event.getMessage()); + assertEquals(1234567890L, event.getNanoTime()); + assertSame(stackTraceElement, event.getSource()); + assertSame(threadName, event.getThreadName()); + assertSame(exception, event.getThrown()); + assertEquals(987654321L, event.getTimeMillis()); + + final LogEvent event2 = builder(event).build(); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); + + assertEquals(contextData, event2.getContextData()); + assertSame(contextStack, event2.getContextStack()); + assertTrue(event2.isEndOfBatch()); + assertTrue(event2.isIncludeLocation()); + assertSame(Level.FATAL, event2.getLevel()); + assertSame(fqcn, event2.getLoggerFqcn()); + assertSame(name, event2.getLoggerName()); + assertSame(marker, event2.getMarker()); + assertSame(message, event2.getMessage()); + assertEquals(1234567890L, event2.getNanoTime()); + assertSame(stackTraceElement, event2.getSource()); + assertSame(threadName, event2.getThreadName()); + assertSame(exception, event2.getThrown()); + assertEquals(987654321L, event2.getTimeMillis()); + + final StringMap differentMap = ContextDataFactory.emptyFrozenContextData(); + different("different contextMap", builder(event).setContextData(differentMap), event); + different("null contextMap", builder(event).setContextData(null), event); + + ThreadContext.push("abc"); + final ContextStack contextStack2 = ThreadContext.getImmutableStack(); + different("different contextStack", builder(event).setContextStack(contextStack2), event); + different("null contextStack", builder(event).setContextStack(null), event); + + different("different EndOfBatch", builder(event).setEndOfBatch(false), event); + different("different IncludeLocation", builder(event).setIncludeLocation(false), event); + + different("different level", builder(event).setLevel(Level.INFO), event); + different("null level", builder(event).setLevel(null), event); + + different("different fqcn", builder(event).setLoggerFqcn("different"), event); + different("null fqcn", builder(event).setLoggerFqcn(null), event); + + different("different name", builder(event).setLoggerName("different"), event); + 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); + 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); + + final StackTraceElement stack2 = new StackTraceElement("XXX", "YYY", "file", 123); + different("different source", builder(event).setSource(stack2), event); + different("null source", builder(event).setSource(null), event); + + different("different threadname", builder(event).setThreadName("different"), event); + different("null threadname", builder(event).setThreadName(null), event); + + different("different exception", builder(event).setThrown(new Error("Boo!")), event); + different("null exception", builder(event).setThrown(null), event); + } + + private static Log4jLogEvent.Builder builder(final LogEvent event) { + return new Log4jLogEvent.Builder(event); + } + + private void different(final String reason, final Log4jLogEvent.Builder builder, final LogEvent event) { + final LogEvent other = builder.build(); + assertNotEquals(other, event, reason); + assertNotEquals(other.hashCode(), event.hashCode(), reason + " hashCode"); + } + + @Test + void testToString() { + // Throws an NPE in 2.6.2 + assertNotNull(new Log4jLogEvent().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..092c2133e23 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java @@ -0,0 +1,369 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.apache.logging.log4j.test.junit.SerialUtil.deserialize; +import static org.apache.logging.log4j.test.junit.SerialUtil.serialize; +import static org.assertj.core.api.Assertions.assertThat; +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.assertNotEquals; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.spi.MutableThreadContextStack; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.junit.jupiter.api.Test; + +/** + * Tests the MutableLogEvent class. + */ +class MutableLogEventTest { + private static final StringMap CONTEXT_DATA = createContextData(); + private static final ThreadContext.ContextStack STACK = new MutableThreadContextStack(Arrays.asList("abc", "xyz")); + + private static StringMap createContextData() { + final StringMap result = new SortedArrayStringMap(); + result.putValue("a", "1"); + result.putValue("b", "2"); + return result; + } + + @Test + void testToImmutable() { + final LogEvent logEvent = new MutableLogEvent(); + assertNotSame(logEvent, logEvent.toImmutable()); + } + + @Test + 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 + void testInitFromReusableCopiesFormatString() { + final 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"); + final 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"); + + final Message eventMementoMessage = mutable.toImmutable().getMessage(); + assertEquals("msg in a {}", eventMementoMessage.getFormat(), "format"); + assertEquals("msg in a bottle", eventMementoMessage.getFormattedMessage(), "formatted"); + assertArrayEquals(new String[] {"bottle"}, eventMementoMessage.getParameters(), "parameters"); + + final 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 + void testInitFromReusableObjectCopiesParameter() { + final Object param = new Object(); + final 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"); + final Message memento = mutable.memento(); + assertNull(memento.getFormat(), "format"); + assertEquals(param.toString(), memento.getFormattedMessage(), "formatted"); + assertArrayEquals(new Object[] {param}, memento.getParameters(), "parameters"); + } + + @Test + void testClear() { + final MutableLogEvent mutable = new MutableLogEvent(); + // initialize the event with an empty message + final 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 + void testJavaIoSerializable() { + 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.getContextMap(), evt2.getContextMap()); + 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 + void testJavaIoSerializableWithThrown() { + 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.getContextMap(), evt2.getContextMap()); + 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()); + } + + @Test + void testPreservesLocation() { + final StackTraceElement source = new RuntimeException().getStackTrace()[0]; + final MutableLogEvent mutable = new MutableLogEvent(); + mutable.setSource(source); + mutable.setIncludeLocation(false); + final Log4jLogEvent immutable = mutable.toImmutable(); + assertThat(immutable.getSource()).isEqualTo(source); + } +} 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..d1a8d61451a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromThrowableMessageTest.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.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Set; +import org.apache.logging.log4j.LogManager; +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; + +/** + * Test for LOG4J2-2368. + */ +public class NestedLoggingFromThrowableMessageTest { + + private static final File file1 = new File("target/NestedLoggerTest1.log"); + private static final File file2 = new File("target/NestedLoggerTest2.log"); + + @BeforeClass + public static void beforeClass() { + file1.delete(); + file2.delete(); + System.setProperty("log4j2.enableThreadlocals", "true"); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule("log4j-nested-logging-throwable-message.xml"); + + private Logger logger; + + @Before + public void before() { + logger = LogManager.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); + + final Set lines1 = readUniqueLines(file1); + final 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(final File input) throws IOException { + final Set lines = new HashSet<>(); + try (final BufferedReader reader = + new BufferedReader(new InputStreamReader(Files.newInputStream(input.toPath())))) { + String line; + while ((line = reader.readLine()) != null) { + assertTrue("Read duplicate line: " + line, lines.add(line)); + } + } + return lines; + } +} 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..a970031732c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.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.impl; + +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.assertSame; + +import java.util.concurrent.atomic.AtomicReference; +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.junit.jupiter.api.Test; + +/** + * Tests the ReusableLogEventFactory class. + */ +class ReusableLogEventFactoryTest { + + @Test + void testCreateEventReturnsDifferentInstanceIfNotReleased() { + final ReusableLogEventFactory factory = new ReusableLogEventFactory(); + 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 + void testCreateEventReturnsSameInstance() { + final ReusableLogEventFactory factory = new ReusableLogEventFactory(); + 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 + void testCreateEventOverwritesFields() { + final ReusableLogEventFactory factory = new ReusableLogEventFactory(); + 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 + void testCreateEventReturnsThreadLocalInstance() throws Exception { + final ReusableLogEventFactory factory = new ReusableLogEventFactory(); + final AtomicReference event1 = new AtomicReference<>(); + final AtomicReference event2 = new AtomicReference<>(); + final Thread t1 = new Thread("THREAD 1") { + @Override + public void run() { + event1.set(callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null)); + } + }; + final Thread t2 = new Thread("Thread 2") { + @Override + public void run() { + event2.set(callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null)); + } + }; + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertNotNull(event1.get()); + assertNotNull(event2.get()); + assertNotSame(event1.get(), event2.get()); + assertEquals("a", event1.get().getLoggerName(), "logger"); + assertEquals(Level.DEBUG, event1.get().getLevel(), "level"); + assertEquals(new SimpleMessage("abc"), event1.get().getMessage(), "msg"); + assertEquals("THREAD 1", event1.get().getThreadName(), "thread name"); + assertEquals(t1.getId(), event1.get().getThreadId(), "tid"); + + assertEquals("b", event2.get().getLoggerName(), "logger"); + assertEquals(Level.INFO, event2.get().getLevel(), "level"); + assertEquals(new SimpleMessage("xyz"), event2.get().getMessage(), "msg"); + assertEquals("Thread 2", event2.get().getThreadName(), "thread name"); + assertEquals(t2.getId(), event2.get().getThreadId(), "tid"); + ReusableLogEventFactory.release(event1.get()); + ReusableLogEventFactory.release(event2.get()); + } + + @SuppressWarnings("deprecation") + @Test + void testCreateEventInitFieldsProperly() { + final ReusableLogEventFactory factory = new ReusableLogEventFactory(); + final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null); + try { + assertNotNull(event.getContextMap()); + 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..5f809beb22e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.util.Arrays.asList; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.apache.logging.log4j.core.impl.ContextDataInjectorFactory.createInjector; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +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.ThreadContextMap; +import org.apache.logging.log4j.util.ProviderUtil; +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; + +@RunWith(Parameterized.class) +public class ThreadContextDataInjectorTest { + @Parameters(name = "{0}") + public static Collection threadContextMapClassNames() { + return asList(new String[][] { + {"org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"}, + {"org.apache.logging.log4j.spi.DefaultThreadContextMap"} + }); + } + + @Parameter + public String threadContextMapClassName; + + private static void resetThreadContextMap() { + final Log4jProvider provider = (Log4jProvider) ProviderUtil.getProvider(); + provider.resetThreadContextMap(); + ThreadContext.init(); + } + + @Before + public void before() { + System.setProperty("log4j2.threadContextMap", threadContextMapClassName); + } + + @After + public void after() { + ThreadContext.remove("foo"); + ThreadContext.remove("baz"); + System.clearProperty("log4j2.threadContextMap"); + System.clearProperty("log4j2.isThreadContextMapInheritable"); + } + + private void testContextDataInjector() { + final ThreadContextMap threadContextMap = ProviderUtil.getProvider().getThreadContextMapInstance(); + assertThat( + "thread context map class name", + threadContextMap.getClass().getName(), + is(equalTo(threadContextMapClassName))); + + final ContextDataInjector contextDataInjector = createInjector(); + final 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(final boolean isThreadContextMapInheritable) { + System.setProperty("log4j2.isThreadContextMapInheritable", Boolean.toString(isThreadContextMapInheritable)); + resetThreadContextMap(); + ThreadContext.clearMap(); + ThreadContext.put("foo", "bar"); + } + + @Test + public void testThreadContextImmutability() { + prepareThreadContext(false); + testContextDataInjector(); + } + + @Test + public void testInheritableThreadContextImmutability() throws Throwable { + prepareThreadContext(true); + try { + newSingleThreadExecutor().submit(this::testContextDataInjector).get(); + } catch (final ExecutionException ee) { + throw ee.getCause(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextTestAccess.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextTestAccess.java new file mode 100644 index 00000000000..661023a46f5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextTestAccess.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.util.ProviderUtil; + +/** + *

+ * 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() { + final Log4jProvider provider = (Log4jProvider) ProviderUtil.getProvider(); + provider.resetThreadContextMap(); + ThreadContext.init(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..10dea802505 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java @@ -0,0 +1,521 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.apache.logging.log4j.util.Strings.toRootUpperCase; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.core.pattern.AnsiEscape; +import org.apache.logging.log4j.core.pattern.JAnsiTextRenderer; +import org.apache.logging.log4j.core.pattern.TextRenderer; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@code ThrowableFormatOptions}. + */ +final class ThrowableFormatOptionsTest { + + /** + * Runs a given test comparing against the expected values. + * + * @param options + * The list of options to parse. + * @param expectedLines + * The expected lines. + * @param expectedPackages + * The expected package filters. + * @param expectedSeparator + * The expected separator. + */ + private static ThrowableFormatOptions test( + final String[] options, + final int expectedLines, + final String expectedSeparator, + final List expectedPackages) { + final ThrowableFormatOptions tfo = ThrowableFormatOptions.newInstance(options); + 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; + } + + /** + * Test {@code %throwable} with null options. + */ + @Test + void testNull() { + test(null, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable} + */ + @Test + void testEmpty() { + test(new String[] {}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{} } with null option value. + */ + @Test + void testOneNullElement() { + test(new String[] {null}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{} } + */ + @Test + void testOneEmptyElement() { + test(new String[] {""}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{full} } + */ + @Test + void testFull() { + test(new String[] {"full"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{full}{ansi} } + */ + @Test + void testFullAnsi() { + final ThrowableFormatOptions tfo = + test(new String[] {"full", "ansi"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + testFullAnsiEmptyConfig(tfo); + } + + /** + * Test {@code %throwable{full}{ansi()} } + */ + @Test + void testFullAnsiEmptyConfig() { + final ThrowableFormatOptions tfo = + test(new String[] {"full", "ansi()"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + testFullAnsiEmptyConfig(tfo); + } + + private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { + final TextRenderer textRenderer = tfo.getTextRenderer(); + assertNotNull(textRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); + // We have defaults + assertFalse(styleMap.isEmpty()); + assertNotNull(styleMap.get(toRootUpperCase("Name"))); + } + + /** + * Test {@code %throwable{full}{ansi(Warning=red))} } + */ + @Test + void testFullAnsiWithCustomStyle() { + final ThrowableFormatOptions tfo = + test(new String[] {"full", "ansi(Warning=red)"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + final TextRenderer textRenderer = tfo.getTextRenderer(); + assertNotNull(textRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); + } + + /** + * Test {@code %throwable{full}{ansi(Warning=red Key=blue Value=cyan))} } + */ + @Test + 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(); + assertNotNull(textRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get(toRootUpperCase("Key"))).isEqualTo(AnsiEscape.createSequence("BLUE")); + assertThat(styleMap.get(toRootUpperCase("Value"))).isEqualTo(AnsiEscape.createSequence("CYAN")); + } + + /** + * Test {@code %throwable{full}{ansi(Warning=red Key=blue,bg_red Value=cyan,bg_black,underline)} } + */ + @Test + void testFullAnsiWithCustomComplexStyles() { + final ThrowableFormatOptions tfo = test( + 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(); + assertNotNull(textRenderer); + assertInstanceOf(JAnsiTextRenderer.class, textRenderer); + final JAnsiTextRenderer ansiRenderer = (JAnsiTextRenderer) textRenderer; + final Map styleMap = ansiRenderer.getStyleMap(); + assertThat(styleMap.get(toRootUpperCase("Warning"))).isEqualTo(AnsiEscape.createSequence("RED")); + assertThat(styleMap.get(toRootUpperCase("Key"))).isEqualTo(AnsiEscape.createSequence("BLUE", "BG_RED")); + assertThat(styleMap.get(toRootUpperCase("Value"))) + .isEqualTo(AnsiEscape.createSequence("CYAN", "BG_BLACK", "UNDERLINE")); + } + + /** + * Test {@code %throwable{none} } + */ + @Test + void testNone() { + test(new String[] {"none"}, 0, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{short} } + */ + @Test + void testShort() { + test(new String[] {"short"}, 2, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{10} } + */ + @Test + void testDepth() { + test(new String[] {"10"}, 10, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{separator(|)} } + */ + @Test + void testSeparator() { + test(new String[] {"separator(|)"}, Integer.MAX_VALUE, "|", null); + } + + /** + * Test {@code %throwable{separator()} } + */ + @Test + void testSeparatorAsEmpty() { + test(new String[] {"separator()"}, Integer.MAX_VALUE, Strings.EMPTY, null); + } + + /** + * Test {@code %throwable{separator(\n)} } + */ + @Test + void testSeparatorAsDefaultLineSeparator() { + test( + new String[] {"separator(" + Strings.LINE_SEPARATOR + ')'}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + null); + } + + /** + * Test {@code %throwable{separator( | )} } + */ + @Test + void testSeparatorAsMultipleCharacters() { + test(new String[] {"separator( | )"}, Integer.MAX_VALUE, " | ", null); + } + + /** + * Test {@code %throwable{full}{separator(|)} } + */ + @Test + void testFullAndSeparator() { + test(new String[] {"full", "separator(|)"}, Integer.MAX_VALUE, "|", null); + } + + /** + * Test {@code %throwable{full}{filters(org.junit)}{separator(|)} } + */ + @Test + void testFullAndFiltersAndSeparator() { + test( + new String[] {"full", "filters(org.junit)", "separator(|)"}, + Integer.MAX_VALUE, + "|", + Collections.singletonList("org.junit")); + } + + /** + * Test {@code %throwable{none}{separator(|)} } + */ + @Test + void testNoneAndSeparator() { + test(new String[] {"none", "separator(|)"}, 0, "|", null); + } + + /** + * Test {@code %throwable{short}{separator(|)} } + */ + @Test + void testShortAndSeparator() { + test(new String[] {"short", "separator(|)"}, 2, "|", null); + } + + /** + * Test {@code %throwable{10}{separator(|)} } + */ + @Test + void testDepthAndSeparator() { + test(new String[] {"10", "separator(|)"}, 10, "|", null); + } + + /** + * Test {@code %throwable{filters(packages)} } + */ + @Test + void testFilters() { + test( + new String[] {"filters(packages)"}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{filters()} } + */ + @Test + void testFiltersAsEmpty() { + test(new String[] {"filters()"}, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); + } + + /** + * Test {@code %throwable{filters(package1,package2)} } + */ + @Test + void testFiltersAsMultiplePackages() { + test( + new String[] {"filters(package1,package2)"}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + Arrays.asList("package1", "package2")); + } + + /** + * Test {@code %throwable{full}{filters(packages)} } + */ + @Test + void testFullAndFilters() { + test( + new String[] {"full", "filters(packages)"}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{none}{filters(packages)} } + */ + @Test + void testNoneAndFilters() { + test( + new String[] {"none", "filters(packages)"}, + 0, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{short}{filters(packages)} } + */ + @Test + void testShortAndFilters() { + test( + new String[] {"short", "filters(packages)"}, + 2, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{10}{filters(packages)} } + */ + @Test + void testDepthAndFilters() { + test( + new String[] {"10", "filters(packages)"}, + 10, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{full}{separator(|)}{filters(packages)} } + */ + @Test + void testFullAndSeparatorAndFilter() { + test( + new String[] {"full", "separator(|)", "filters(packages)"}, + Integer.MAX_VALUE, + "|", + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{full}{separator(|)}{filters(package1,package2)} } + */ + @Test + void testFullAndSeparatorAndFilters() { + test( + new String[] {"full", "separator(|)", "filters(package1,package2)"}, + Integer.MAX_VALUE, + "|", + Arrays.asList("package1", "package2")); + } + + /** + * Test {@code %throwable{none}{separator(|)}{filters(packages)} } + */ + @Test + void testNoneAndSeparatorAndFilters() { + test(new String[] {"none", "separator(|)", "filters(packages)"}, 0, "|", Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{short}{separator(|)}{filters(packages)} } + */ + @Test + void testShortAndSeparatorAndFilters() { + test( + new String[] {"short", "separator(|)", "filters(packages)"}, + 2, + "|", + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{10}{separator(|)}{filters(packages)} } + */ + @Test + void testDepthAndSeparatorAndFilters() { + test(new String[] {"10", "separator(|)", "filters(packages)"}, 10, "|", Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{full,filters(packages)} } + */ + @Test + void testSingleOptionFullAndFilters() { + test( + new String[] {"full,filters(packages)"}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{none,filters(packages)} } + */ + @Test + void testSingleOptionNoneAndFilters() { + test(new String[] {"none,filters(packages)"}, 0, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{short,filters(packages)} } + */ + @Test + void testSingleOptionShortAndFilters() { + test( + new String[] {"short,filters(packages)"}, + 2, + Strings.LINE_SEPARATOR, + Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{none,filters(packages)} } + */ + @Test + void testSingleOptionDepthAndFilters() { + test(new String[] {"10,filters(packages)"}, 10, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); + } + + /** + * Test {@code %throwable{full,filters(package1,package2)} } + */ + @Test + void testSingleOptionFullAndMultipleFilters() { + test( + new String[] {"full,filters(package1,package2)"}, + Integer.MAX_VALUE, + Strings.LINE_SEPARATOR, + Arrays.asList("package1", "package2")); + } + + /** + * Test {@code %throwable{none,filters(package1,package2)} } + */ + @Test + void testSingleOptionNoneAndMultipleFilters() { + test( + new String[] {"none,filters(package1,package2)"}, + 0, + Strings.LINE_SEPARATOR, + Arrays.asList("package1", "package2")); + } + + /** + * Test {@code %throwable{short,filters(package1,package2)} } + */ + @Test + void testSingleOptionShortAndMultipleFilters() { + test( + new String[] {"short,filters(package1,package2)"}, + 2, + Strings.LINE_SEPARATOR, + Arrays.asList("package1", "package2")); + } + + /** + * Test {@code %throwable{none,filters(package1,package2)} } + */ + @Test + void testSingleOptionDepthAndMultipleFilters() { + test( + new String[] {"10,filters(package1,package2)"}, + 10, + Strings.LINE_SEPARATOR, + Arrays.asList("package1", "package2")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429MyNamesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429MyNamesTest.java new file mode 100644 index 00000000000..a0114d9717b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429MyNamesTest.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.jackson; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.util.ClassUtil; +import java.io.IOException; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("Layouts.Json") +class JacksonIssue429MyNamesTest { + + @SuppressWarnings("serial") + static class MyStackTraceElementDeserializer extends StdScalarDeserializer { + private static final long serialVersionUID = 1L; + + public static final MyStackTraceElementDeserializer instance = new MyStackTraceElementDeserializer(); + + public MyStackTraceElementDeserializer() { + super(StackTraceElement.class); + } + + @Override + public StackTraceElement deserialize(final JsonParser jp, final DeserializationContext ctxt) + throws IOException { + JsonToken t = jp.getCurrentToken(); + // Must get an Object + if (t == JsonToken.START_OBJECT) { + String className = Strings.EMPTY, methodName = Strings.EMPTY, fileName = Strings.EMPTY; + 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 { + throw JsonMappingException.from( + jp, "Non-numeric token (" + t + ") for property 'lineNumber'"); + } + } else if ("method".equals(propName)) { + methodName = jp.getText(); + } else if ("nativeMethod".equals(propName)) { + // no setter, not passed via constructor: ignore + } else { + handleUnknownProperty(jp, ctxt, _valueClass, propName); + } + } + return new StackTraceElement(className, methodName, fileName, lineNumber); + } + throw JsonMappingException.from( + jp, + String.format( + "Cannot deserialize instance of %s out of %s token", + ClassUtil.nameOf(this._valueClass), t)); + } + } + + static class StackTraceBean { + public static final int NUM = 13; + + @JsonProperty("Location") + @JsonDeserialize(using = MyStackTraceElementDeserializer.class) + private StackTraceElement location; + } + + private static final ObjectMapper SHARED_MAPPER = new ObjectMapper(); + + private final ObjectMapper MAPPER = objectMapper(); + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + protected ObjectMapper objectMapper() { + return SHARED_MAPPER; + } + + @Test + void testStackTraceElementWithCustom() throws Exception { + // first, via bean that contains StackTraceElement + final StackTraceBean bean = MAPPER.readValue( + aposToQuotes( + "{'Location':{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':13}}"), + StackTraceBean.class); + assertNotNull(bean); + assertNotNull(bean.location); + assertEquals(StackTraceBean.NUM, bean.location.getLineNumber()); + + // and then directly, iff registered + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new MyStackTraceElementDeserializer()); + mapper.registerModule(module); + + final StackTraceElement elem = mapper.readValue( + aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':13}"), + StackTraceElement.class); + assertNotNull(elem); + assertEquals(StackTraceBean.NUM, elem.getLineNumber()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429Test.java new file mode 100644 index 00000000000..e2ccda0a49d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/JacksonIssue429Test.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.jackson; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("Layouts.Json") +class JacksonIssue429Test { + + @SuppressWarnings("serial") + static class Jackson429StackTraceElementDeserializer extends StdDeserializer { + private static final long serialVersionUID = 1L; + + public Jackson429StackTraceElementDeserializer() { + super(StackTraceElement.class); + } + + @Override + public StackTraceElement deserialize(final JsonParser jp, final DeserializationContext ctxt) + throws IOException { + jp.skipChildren(); + return new StackTraceElement("a", "b", "b", StackTraceBean.NUM); + } + } + + static class StackTraceBean { + public static final int NUM = 13; + + @JsonProperty("Location") + @JsonDeserialize(using = Jackson429StackTraceElementDeserializer.class) + private StackTraceElement location; + } + + private static final ObjectMapper SHARED_MAPPER = new ObjectMapper(); + + private final ObjectMapper MAPPER = objectMapper(); + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + protected ObjectMapper objectMapper() { + return SHARED_MAPPER; + } + + @Test + void testStackTraceElementWithCustom() throws Exception { + // first, via bean that contains StackTraceElement + final StackTraceBean bean = MAPPER.readValue(aposToQuotes("{'Location':'foobar'}"), StackTraceBean.class); + assertNotNull(bean); + assertNotNull(bean.location); + assertEquals(StackTraceBean.NUM, bean.location.getLineNumber()); + + // and then directly, iff registered + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new Jackson429StackTraceElementDeserializer()); + mapper.registerModule(module); + + final StackTraceElement elem = mapper.readValue( + aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"), + StackTraceElement.class); + assertNotNull(elem); + assertEquals(StackTraceBean.NUM, elem.getLineNumber()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java new file mode 100644 index 00000000000..01dca8d5139 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.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.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class LevelMixInJsonTest extends LevelMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jJsonObjectMapper(false, true, false, false); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInTest.java new file mode 100644 index 00000000000..aee3d63005f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInTest.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.jackson; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import java.io.IOException; +import java.util.Objects; +import org.apache.logging.log4j.Level; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link LevelMixIn}. + */ +public abstract class LevelMixInTest { + + static class Fixture { + @JsonProperty + private final Level level = Level.DEBUG; + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Fixture other = (Fixture) obj; + if (!Objects.equals(this.level, other.level)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return 31 + Objects.hashCode(level); + } + } + + private ObjectMapper log4jObjectMapper; + + private ObjectReader reader; + + private ObjectWriter writer; + + protected abstract ObjectMapper newObjectMapper(); + + @BeforeEach + public void setUp() { + log4jObjectMapper = newObjectMapper(); + writer = log4jObjectMapper.writer(); + reader = log4jObjectMapper.readerFor(Level.class); + } + + @Test + public void testContainer() throws IOException { + final Fixture expected = new Fixture(); + final String str = writer.writeValueAsString(expected); + assertTrue(str.contains("DEBUG")); + final ObjectReader fixtureReader = log4jObjectMapper.readerFor(Fixture.class); + final Fixture actual = fixtureReader.readValue(str); + assertEquals(expected, actual); + } + + @Test + public void testNameOnly() throws IOException { + final Level expected = Level.getLevel("DEBUG"); + final String str = writer.writeValueAsString(expected); + assertTrue(str.contains("DEBUG")); + final Level actual = reader.readValue(str); + assertEquals(expected, actual); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInXmlTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInXmlTest.java new file mode 100644 index 00000000000..acc7d439582 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInXmlTest.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.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class LevelMixInXmlTest extends LevelMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jXmlObjectMapper(); + } + + @Test + @Disabled("String-like objects like Level do not work as root elements.") + @Override + public void testNameOnly() throws IOException { + // Disabled: see https://github.com/FasterXML/jackson-dataformat-xml + super.testNameOnly(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.java new file mode 100644 index 00000000000..8c1874b169a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInYamlTest.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.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class LevelMixInYamlTest extends LevelMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jYamlObjectMapper(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java new file mode 100644 index 00000000000..2d73af1c432 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.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.jackson; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("Layouts.Json") +class StackTraceElementMixInTest { + + @Test + void testLog4jJsonObjectMapper() throws Exception { + this.roundtrip(new Log4jJsonObjectMapper()); + } + + @Test + void testLog4jYamlObjectMapper() throws Exception { + this.roundtrip(new Log4jYamlObjectMapper()); + } + + /** + * @param mapper + * @throws JsonProcessingException + * @throws IOException + * @throws JsonParseException + * @throws JsonMappingException + */ + private void roundtrip(final ObjectMapper mapper) + throws JsonProcessingException, IOException, JsonParseException, JsonMappingException { + final StackTraceElement expected = + new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = mapper.writeValueAsString(expected); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + assertEquals(expected, actual); + } + + @Test + void testLog4jXmlObjectMapper() throws Exception { + this.roundtrip(new Log4jXmlObjectMapper()); + } + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + @Test + void testFromJsonWithSimpleModule() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); + mapper.registerModule(module); + final StackTraceElement expected = + new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = this.aposToQuotes( + "{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + assertEquals(expected, actual); + } + + @Test + void testFromJsonWithLog4jModule() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final boolean encodeThreadContextAsList = false; + final SimpleModule module = new Log4jJsonModule(encodeThreadContextAsList, true, false, false); + module.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); + mapper.registerModule(module); + final StackTraceElement expected = + new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = this.aposToQuotes( + "{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + assertEquals(expected, actual); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..501988fa3f2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.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.jmx; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.management.ObjectName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Server class. + */ +class ServerTest { + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // no MalformedObjectNameException = success + } + + @Test + 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)); + // 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..d15961bca51 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.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.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.nio.charset.Charset; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; +import org.apache.logging.log4j.core.layout.PatternLayout.SerializerBuilder; +import org.junit.jupiter.api.Test; + +/** + * Tests AbstractStringLayout. + */ +class AbstractStringLayoutTest { + + // Configuration explicitly null + private static final Serializer serializer = new SerializerBuilder() + .setPattern(DefaultConfiguration.DEFAULT_PATTERN) + .build(); + + 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() { + // Configuration explicitly null + super(null, Charset.defaultCharset(), serializer, serializer); + } + + public static StringBuilder getStringBuilder() { + return AbstractStringLayout.getStringBuilder(); + } + + @Override + public String toSerializable(final LogEvent event) { + return null; + } + } + + @Test + void testGetStringBuilderCapacityRestrictedToMax() { + 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"); + } + + @Test + void testNullConfigurationIsAllowed() { + try { + final ConcreteStringLayout layout = new ConcreteStringLayout(); + layout.serializeToString(serializer); + } catch (NullPointerException e) { + fail(e); + } + } +} 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..5736383b419 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.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 static org.hamcrest.CoreMatchers.both; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; + +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 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; + +/** + * Test for LOG4J2-1769, kind of. + * + * @since 2.8 + */ +@Tag("concurrency") +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") + 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 (final 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/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java index 0e4ad52ecc4..8224b7473f6 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithJsonLayoutTest.java @@ -1,21 +1,25 @@ /* * 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.logging.log4j.core.layout; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.MatcherAssert.assertThat; + import java.io.File; import java.nio.charset.Charset; import java.nio.file.Files; @@ -24,17 +28,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; - 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.ClassRule; import org.junit.Test; -import static org.hamcrest.CoreMatchers.*; - -import static org.junit.Assert.*; - /** * Test for LOG4J2-1769. * @@ -43,6 +42,7 @@ public class ConcurrentLoggingWithJsonLayoutTest { @ClassRule public static LoggerContextRule context = new LoggerContextRule("log4j2-json-layout.xml"); + private static final String PATH = "target/test-json-layout.log"; @AfterClass @@ -62,12 +62,7 @@ public void testConcurrentLogging() throws Throwable { // Appender is configured with ignoreExceptions="false"; // any exceptions are propagated to the caller, so we can catch them here. - t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(final Thread t, final Throwable e) { - thrown.add(e); - } - }); + t.setUncaughtExceptionHandler((t1, e) -> thrown.add(e)); t.start(); } @@ -85,7 +80,7 @@ public void uncaughtException(final Thread t, final Throwable e) { if (new File(PATH).exists()) { final List lines = Files.readAllLines(new File(PATH).toPath(), Charset.defaultCharset()); for (final String line : lines) { - assertThat(line, startsWith("{\"thread\":")); + assertThat(line, containsString("\"thread\":")); assertThat(line, endsWith("\"threadPriority\":5}")); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java index 0ee8861d4d5..fadd4a0fecf 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.layout; @@ -21,17 +21,16 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; - import org.apache.commons.csv.CSVFormat; import org.apache.logging.log4j.Level; -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.junit.ThreadContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.test.junit.ThreadContextRule; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -49,7 +48,7 @@ public class CsvLogEventLayoutTest { static ConfigurationFactory cf = new BasicConfigurationFactory(); @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); + public final ThreadContextRule threadContextRule = new ThreadContextRule(); @AfterClass public static void cleanupClass() { @@ -69,8 +68,8 @@ public static void setupClass() { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(null, "Excel", null, null, null, null, null, - null, StandardCharsets.UTF_16, null, null); + final AbstractCsvLayout layout = CsvLogEventLayout.createLayout( + null, "Excel", null, null, null, null, null, null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } @@ -78,8 +77,8 @@ public void testCustomCharset() { public void testHeaderFooter() { final String header = "# Header"; final String footer = "# Footer "; - final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(ctx.getConfiguration(), "Excel", null, null, - null, null, null, null, null, header, footer); + final AbstractCsvLayout layout = CsvLogEventLayout.createLayout( + ctx.getConfiguration(), "Excel", null, null, null, null, null, null, null, header, footer); testLayout(CSVFormat.DEFAULT, layout, header, footer); } @@ -99,7 +98,8 @@ private void testLayout(final CSVFormat format) { testLayout(format, CsvLogEventLayout.createLayout(format), null, null); } - private void testLayout(final CSVFormat format, final AbstractCsvLayout layout, final String header, final String footer) { + private void testLayout( + final CSVFormat format, final AbstractCsvLayout layout, final String header, final String footer) { final Map appenders = root.getAppenders(); for (final Appender appender : appenders.values()) { root.removeAppender(appender); @@ -127,7 +127,7 @@ private void testLayout(final CSVFormat format, final AbstractCsvLayout layout, final String quote = del == ',' ? "\"" : ""; Assert.assertTrue(event0, event0.contains(del + quote + "one=1, two=2, three=3" + quote + del)); Assert.assertTrue(event1, event1.contains(del + "INFO" + del)); - + if (hasHeaderSerializer && header == null) { Assert.fail(); } @@ -149,27 +149,27 @@ private void testLayout(final CSVFormat format, final AbstractCsvLayout layout, } @Test - public void testLayoutDefault() throws Exception { + public void testLayoutDefault() { testLayout(CSVFormat.DEFAULT); } @Test - public void testLayoutExcel() throws Exception { + public void testLayoutExcel() { testLayout(CSVFormat.EXCEL); } @Test - public void testLayoutMySQL() throws Exception { + public void testLayoutMySQL() { testLayout(CSVFormat.MYSQL); } @Test - public void testLayoutRFC4180() throws Exception { + public void testLayoutRFC4180() { testLayout(CSVFormat.RFC4180); } @Test - public void testLayoutTab() throws Exception { + public void testLayoutTab() { testLayout(CSVFormat.TDF); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutAllAsyncTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutAllAsyncTest.java new file mode 100644 index 00000000000..1bcb5ffcf22 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutAllAsyncTest.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.commons.csv.CSVFormat; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link AbstractCsvLayout} with all loggers async. + * + * @since 2.6 + */ +@Tag("Layouts.Csv") +class CsvParameterLayoutAllAsyncTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerTest.xml"); + } + + @AfterAll + static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + } + + @Test + void testLayoutDefaultNormal() throws Exception { + final Logger root = (Logger) LogManager.getRootLogger(); + CsvParameterLayoutTest.testLayoutNormalApi(root, CsvParameterLayout.createDefaultLayout(), false); + } + + @Test + void testLayoutDefaultObjectArrayMessage() throws Exception { + final Logger root = (Logger) LogManager.getRootLogger(); + CsvParameterLayoutTest.testLayoutNormalApi(root, CsvParameterLayout.createDefaultLayout(), true); + } + + @Test + void testLayoutTab() throws Exception { + final Logger root = (Logger) LogManager.getRootLogger(); + CsvParameterLayoutTest.testLayoutNormalApi(root, CsvParameterLayout.createLayout(CSVFormat.TDF), true); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java index e7d1074efdb..50bff2beac7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.layout; @@ -25,17 +25,16 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import org.apache.commons.csv.CSVFormat; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.categories.Layouts; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.junit.ThreadContextRule; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.ObjectArrayMessage; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.test.junit.ThreadContextRule; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -53,8 +52,14 @@ public class CsvParameterLayoutTest { @Parameterized.Parameters(name = "{0}") public static Collection data() { - return Arrays.asList(new Object[][] { { new LoggerContextRule("csvParamsSync.xml"), }, - { new LoggerContextRule("csvParamsMixedAsync.xml"), }, }); + return Arrays.asList(new Object[][] { + { + new LoggerContextRule("csvParamsSync.xml"), + }, + { + new LoggerContextRule("csvParamsMixedAsync.xml"), + }, + }); } @Rule @@ -69,8 +74,8 @@ public CsvParameterLayoutTest(final LoggerContextRule contextRule) { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvParameterLayout.createLayout(null, "Excel", null, null, null, null, null, - null, StandardCharsets.UTF_16, null, null); + final AbstractCsvLayout layout = CsvParameterLayout.createLayout( + null, "Excel", null, null, null, null, null, null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } @@ -110,7 +115,10 @@ static void testLayoutNormalApi(final Logger root, final AbstractCsvLayout layou // wait until background thread finished processing appender.countDownLatch.await(10, TimeUnit.SECONDS); } - assertEquals("Background thread did not finish processing: msg count", msgCount, appender.getMessages().size()); + assertEquals( + "Background thread did not finish processing: msg count", + msgCount, + appender.getMessages().size()); // don't stop appender until background thread is done appender.stop(); @@ -164,7 +172,7 @@ public void testLayoutTab() throws Exception { @Test public void testLogJsonArgument() throws InterruptedException { - final ListAppender appender = (ListAppender) init.getAppender("List"); + final ListAppender appender = init.getAppender("List"); appender.countDownLatch = new CountDownLatch(4); appender.clear(); final Logger logger = (Logger) LogManager.getRootLogger(); @@ -172,10 +180,13 @@ public void testLogJsonArgument() throws InterruptedException { logger.error("log:{}", json); // wait until background thread finished processing final int msgCount = 1; - if (appender.getMessages().size() < msgCount) { + if (appender.getMessages().isEmpty()) { appender.countDownLatch.await(5, TimeUnit.SECONDS); } - assertEquals("Background thread did not finish processing: msg count", msgCount, appender.getMessages().size()); + assertEquals( + "Background thread did not finish processing: msg count", + msgCount, + appender.getMessages().size()); // don't stop appender until background thread is done appender.stop(); @@ -183,5 +194,4 @@ public void testLogJsonArgument() throws InterruptedException { final String eventStr = list.get(0).toString(); Assert.assertTrue(eventStr, eventStr.contains(json)); } - } 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..47bc55eafde --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.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.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +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.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; + +@LoggerContextSource("GelfLayout2Test.xml") +class GelfLayout2Test { + + @Test + 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..ba67f16a8c2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.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.layout; + +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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +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.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.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("GelfLayout3Test.xml") +@UsingAnyThreadContext +@Tag("json") +class GelfLayout3Test { + + @Test + 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")); + final 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 + 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"); + final 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")); + final 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..9eb4493ac3d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.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.layout; + +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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +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.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.spi.AbstractLogger; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("GelfLayoutPatternSelectorTest.xml") +@UsingAnyThreadContext +@Tag("json") +class GelfLayoutPatternSelectorTest { + + @Test + 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..569a6698511 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.io.JsonStringEncoder; +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.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@UsingAnyThreadContext +class GelfLayoutTest { + + static ConfigurationFactory configFactory = new BasicConfigurationFactory(); + + 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 + static void cleanupClass() { + ConfigurationFactory.removeConfigurationFactory(configFactory); + } + + @BeforeAll + static void setupClass() { + ConfigurationFactory.setConfigurationFactory(configFactory); + final LoggerContext ctx = LoggerContext.getContext(); + 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 + void testLayoutGzipCompression() throws Exception { + testCompressedLayout(CompressionType.GZIP, true, true, HOSTNAME, false, false); + } + + @Test + void testLayoutNoCompression() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, HOSTNAME, false, false); + } + + @Test + void testLayoutZlibCompression() throws Exception { + testCompressedLayout(CompressionType.ZLIB, true, true, HOSTNAME, false, false); + } + + @Test + void testLayoutNoStacktrace() throws Exception { + testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, false, false); + } + + @Test + void testLayoutNoThreadContext() throws Exception { + testCompressedLayout(CompressionType.OFF, true, false, HOSTNAME, false, false); + } + + @Test + void testLayoutNoHost() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, null, false, false); + } + + @Test + void testLayoutNullDelimiter() throws Exception { + testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, true, false); + } + + @Test + void testLayoutNewLineDelimiter() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, HOSTNAME, false, true); + } + + @Test + 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(final String messagePattern, final Boolean requiresLocation) { + + final GelfLayout layout = + GelfLayout.newBuilder().setMessagePattern(messagePattern).build(); + + assertEquals(layout.requiresLocation(), requiresLocation); + } + + @Test + void testRequiresLocationPatternNotSet() { + testRequiresLocation(null, false); + } + + @Test + void testRequiresLocationPatternNotContainsLocation() { + testRequiresLocation("%m %n", false); + } + + @Test + 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..ee7e81ff9ca --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.AbstractLogEvent; +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.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.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@UsingAnyThreadContext +class HtmlLayoutTest { + private static class MyLogEvent extends AbstractLogEvent { + private static final long serialVersionUID = 0; + + @Override + public Instant getInstant() { + final 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(); + + static ConfigurationFactory cf = new BasicConfigurationFactory(); + + @BeforeAll + static void setupClass() { + ConfigurationFactory.setConfigurationFactory(cf); + final LoggerContext ctx = LoggerContext.getContext(); + ctx.reconfigure(); + } + + @AfterAll + static void cleanupClass() { + ConfigurationFactory.removeConfigurationFactory(cf); + } + + private static final String body = + "java.lang.NullPointerException: test"; + + private static final String multiLine = "First line
Second line"; + + @Test + void testDefaultContentType() { + final HtmlLayout layout = HtmlLayout.createDefaultLayout(); + assertEquals("text/html; charset=UTF-8", layout.getContentType()); + } + + @Test + 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 + void testDefaultCharset() { + final HtmlLayout layout = HtmlLayout.createDefaultLayout(); + assertEquals(StandardCharsets.UTF_8, layout.getCharset()); + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + void testLayoutIncludeLocationNo() throws Exception { + testLayout(false); + } + + @Test + void testLayoutIncludeLocationYes() throws Exception { + testLayout(true); + } + + private void testLayout(final boolean includeLocation) { + 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(multiLine, list.get(50), "Incorrect multiline"); + assertTrue(html.contains("HtmlLayoutTest.java:"), "Missing location"); + assertEquals(body, list.get(71), "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 + void testLayoutWithoutDataPattern() { + final HtmlLayout layout = HtmlLayout.newBuilder().build(); + + final MyLogEvent event = new MyLogEvent(); + final String actual = getDateLine(layout.toSerializable(event)); + + final long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + assertEquals("" + (event.getTimeMillis() - jvmStratTime) + "", actual, "Incorrect date:" + actual); + } + + @Test + void testLayoutWithDatePatternJvmElapseTime() { + final HtmlLayout layout = + HtmlLayout.newBuilder().setDatePattern("JVM_ELAPSE_TIME").build(); + + final MyLogEvent event = new MyLogEvent(); + final String actual = getDateLine(layout.toSerializable(event)); + + final long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + assertEquals("" + (event.getTimeMillis() - jvmStratTime) + "", actual, "Incorrect date:" + actual); + } + + @Test + void testLayoutWithDatePatternUnix() { + final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX").build(); + + final MyLogEvent event = new MyLogEvent(); + final String actual = getDateLine(layout.toSerializable(event)); + + assertEquals("" + event.getInstant().getEpochSecond() + "", actual, "Incorrect date:" + actual); + } + + @Test + void testLayoutWithDatePatternUnixMillis() { + final HtmlLayout layout = + HtmlLayout.newBuilder().setDatePattern("UNIX_MILLIS").build(); + + final MyLogEvent event = new MyLogEvent(); + final String actual = getDateLine(layout.toSerializable(event)); + + assertEquals("" + event.getTimeMillis() + "", actual, "Incorrect date:" + actual); + } + + @Test + void testLayoutWithDatePatternFixedFormat() { + for (final String timeZone : new String[] {"GMT+8", "GMT+0530", "UTC", null}) { + for (final FixedFormat format : FixedFormat.values()) { + testLayoutWithDatePatternFixedFormat(format, timeZone); + } + } + } + + private String getDateLine(final String logEventString) { + return logEventString.split(System.lineSeparator())[2]; + } + + private void testLayoutWithDatePatternFixedFormat(final FixedFormat format, final String timezone) { + final HtmlLayout layout = HtmlLayout.newBuilder() + .setDatePattern(format.name()) + .setTimezone(timezone) + .build(); + + final LogEvent event = new MyLogEvent(); + final String actual = getDateLine(layout.toSerializable(event)); + + // build expected date string + final 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. + final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern( + format.getPattern().replace('n', 'S').replace('X', 'x'), locale); + String expected = zonedDateTime.format(dateTimeFormatter); + + assertEquals( + "" + expected + "", + actual, + MessageFormat.format("Incorrect date={0}, format={1}, timezone={2}", actual, format.name(), timezone)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutMillisTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutMillisTest.java new file mode 100644 index 00000000000..614219e6114 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutMillisTest.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.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.util.List; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * Tests the JsonLayout class with millis. + */ +@Category(Layouts.Json.class) +public class JsonLayoutMillisTest { + + private static final String CONFIG = "log4j2-json-layout-timestamp.xml"; + + private ListAppender app; + + @Rule + public LoggerContextRule context = new LoggerContextRule(CONFIG); + + private Logger logger; + + private void assertEventCount(final List events, final int expected) { + assertEquals("Incorrect number of events.", expected, events.size()); + } + + @Before + public void before() { + logger = context.getLogger("LayoutTest"); + // + app = context.getListAppender("List").clear(); + } + + @Test + public void testTimestamp() { + logger.info("This is a test message"); + final List message = app.getMessages(); + assertTrue("No messages", message != null && !message.isEmpty()); + final String json = message.get(0); + System.out.println(json); + assertNotNull("No JSON message", json); + assertTrue("No timestamp", json.contains("\"timeMillis\":")); + assertFalse("Instant is present", json.contains("instant:")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java new file mode 100644 index 00000000000..a0f67ffa0cc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java @@ -0,0 +1,680 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertj.core.api.Assertions.fail; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +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 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.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.async.RingBufferLogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.impl.ContextDataFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; +import org.apache.logging.log4j.core.lookup.JavaLookup; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.util.SystemClock; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests the JsonLayout class. + */ +@Tag("Layouts.Json") +class JsonLayoutTest { + static ConfigurationFactory cf = new BasicConfigurationFactory(); + + private static final String DQUOTE = "\""; + + @AfterAll + static void cleanupClass() { + ConfigurationFactory.removeConfigurationFactory(cf); + ThreadContext.clearAll(); + } + + @BeforeAll + 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(); + assertEquals(expected, trimedLine, "Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine)); + } + + private void checkContains(final String expected, final List list) { + for (final String string : list) { + final String trimedLine = string.trim(); + if (trimedLine.equals(expected)) { + return; + } + } + 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(str.contains(expected), "Cannot find contextMapAslist " + expected + " in " + str); + } else { + // "KEY":"VALUE" + final String expected = String.format("\"%s\":\"%s\"", key, value); + assertTrue(str.contains(expected), "Cannot find contextMap " + expected + " in " + str); + } + } + + 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(str.contains(expected), "Cannot find " + expected + " in " + str); + } + + private void checkPropertyName(final String name, final boolean compact, final String str) { + final String propSep = this.toPropertySeparator(compact); + assertTrue(str.contains(DQUOTE + name + DQUOTE + propSep), str); + } + + private void checkPropertyNameAbsent(final String name, final boolean compact, final String str) { + final String propSep = this.toPropertySeparator(compact); + assertFalse(str.contains(DQUOTE + name + DQUOTE + propSep), str); + } + + private void testAllFeatures( + final boolean locationInfo, + final boolean compact, + final boolean eventEol, + final String endOfLine, + 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) + .setEndOfLine(endOfLine) + .setCharset(StandardCharsets.UTF_8) + .setIncludeStacktrace(includeStacktrace) + .build(); + // @formatter:off + final String str = layout.toSerializable(expected); + this.toPropertySeparator(compact); + if (endOfLine == null) { + // Just check for \n since \r might or might not be there. + assertEquals(!compact || eventEol, str.contains("\n"), str); + } else { + assertEquals(!compact || eventEol, str.contains(endOfLine), str); + assertEquals(compact && eventEol, str.endsWith(endOfLine), str); + } + assertEquals(locationInfo, str.contains("source"), str); + assertEquals(includeContext, str.contains("contextMap"), str); + + 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 + void testContentType() { + final AbstractJacksonLayout layout = JsonLayout.createDefaultLayout(); + assertEquals("application/json; charset=UTF-8", layout.getContentType()); + } + + @Test + void testDefaultCharset() { + final AbstractJacksonLayout layout = JsonLayout.createDefaultLayout(); + assertEquals(StandardCharsets.UTF_8, layout.getCharset()); + } + + @Test + void testEscapeLayout() { + 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 + void testLayout() { + 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 + 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.contains("\"loggerName\":\"a.B\""), str); + final Log4jLogEvent actual = + new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); + assertEquals(expected.getLoggerName(), actual.getLoggerName()); + assertEquals(expected, actual); + } + + @Test + void testAdditionalFields() { + 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.contains("\"KEY1\":\"VALUE1\""), str); + assertTrue(str.contains("\"KEY2\":\"" + new JavaLookup().getRuntime() + "\""), str); + } + + // Test for LOG4J2-2345 + @Test + void testReusableLayoutMessageWithCurlyBraces() throws Exception { + final boolean propertiesAsList = false; + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setPropertiesAsList(propertiesAsList) + .setComplete(false) + .setCompact(true) + .setEventEol(false) + .setCharset(StandardCharsets.UTF_8) + .setIncludeStacktrace(true) + .build(); + final Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + try { + final Log4jLogEvent expected = Log4jLogEvent.newBuilder() + .setLoggerName("a.B") + .setLoggerFqcn("f.q.c.n") + .setLevel(Level.DEBUG) + .setMessage(message) + .setThreadName("threadName") + .setTimeMillis(1) + .build(); + final MutableLogEvent mutableLogEvent = new MutableLogEvent(); + mutableLogEvent.initFrom(expected); + final String str = layout.toSerializable(mutableLogEvent); + final String expectedMessage = "Testing " + TestObj.TO_STRING_VALUE; + assertTrue(str.contains("\"message\":\"" + expectedMessage + '"'), str); + final Log4jLogEvent actual = + new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); + assertEquals(expectedMessage, actual.getMessage().getFormattedMessage()); + } finally { + ReusableMessageFactory.release(message); + } + } + + // Test for LOG4J2-2312 LOG4J2-2341 + @Test + void testLayoutRingBufferEventReusableMessageWithCurlyBraces() throws Exception { + final boolean propertiesAsList = false; + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setPropertiesAsList(propertiesAsList) + .setComplete(false) + .setCompact(true) + .setEventEol(false) + .setCharset(StandardCharsets.UTF_8) + .setIncludeStacktrace(true) + .build(); + final Message message = ReusableMessageFactory.INSTANCE.newMessage("Testing {}", new TestObj()); + try { + final RingBufferLogEvent ringBufferEvent = new RingBufferLogEvent(); + ringBufferEvent.setValues( + null, + "a.B", + null, + "f.q.c.n", + Level.DEBUG, + message, + null, + new SortedArrayStringMap(), + ThreadContext.EMPTY_STACK, + 1L, + "threadName", + 1, + null, + new SystemClock(), + new DummyNanoClock()); + final String str = layout.toSerializable(ringBufferEvent); + final String expectedMessage = "Testing " + TestObj.TO_STRING_VALUE; + assertThat(str, containsString("\"message\":\"" + expectedMessage + '"')); + final Log4jLogEvent actual = + new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); + assertEquals(expectedMessage, actual.getMessage().getFormattedMessage()); + } finally { + ReusableMessageFactory.release(message); + } + } + + static class TestObj { + static final String TO_STRING_VALUE = "This is my toString {} with curly braces"; + + @Override + public String toString() { + return TO_STRING_VALUE; + } + } + + @Test + void testLocationOffCompactOffMdcOff() throws Exception { + this.testAllFeatures(false, false, false, null, false, false, true); + } + + @Test + void testLocationOnCompactOnMdcOn() throws Exception { + this.testAllFeatures(true, true, false, null, true, false, true); + } + + @Test + void testLocationOnCompactOnEventEolOnMdcOn() throws Exception { + this.testAllFeatures(true, true, true, null, true, false, true); + } + + @Test + void testLocationOnCompactOnEventEolOnMdcOnMdcAsList() throws Exception { + this.testAllFeatures(true, true, true, null, true, true, true); + } + + @Test + void testExcludeStacktrace() throws Exception { + this.testAllFeatures(false, false, false, null, false, false, false); + } + + @Test + void testLocationOnCustomEndOfLine() throws Exception { + this.testAllFeatures(true, true, true, "CUSTOM_END_OF_LINE", true, false, true); + } + + @Test + void testStacktraceAsString() { + final String str = prepareJSONForStacktraceTests(true); + assertTrue(str.contains("\"extendedStackTrace\":\"java.lang.NullPointerException"), str); + } + + @Test + void testStacktraceAsNonString() { + final String str = prepareJSONForStacktraceTests(false); + assertTrue(str.contains("\"extendedStackTrace\":["), str); + } + + 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 + void testObjectMessageAsJsonString() { + final String str = prepareJSONForObjectMessageAsJsonObjectTests(1234, false); + assertTrue(str.contains("\"message\":\"" + this.getClass().getCanonicalName() + "$TestClass@"), str); + } + + @Test + void testObjectMessageAsJsonObject() { + final String str = prepareJSONForObjectMessageAsJsonObjectTests(1234, true); + assertTrue(str.contains("\"message\":{\"value\":1234}"), str); + } + + 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 + void testInstantSortsBeforeMessage() { + final String str = prepareJSONForEventWithTimeAsInstant(); + assertTrue(str.startsWith("{\"instant\":{\"epochSecond\":0,\"nanoOfSecond\":1000000},"), str); + assertTrue(str.contains("\"message\":\"message\""), str); + } + + private String prepareJSONForEventWithTimeAsInstant() { + final TestClass testClass = new TestClass(); + // @formatter:off + final Log4jLogEvent expected = Log4jLogEvent.newBuilder() + .setLoggerName("a.B") + .setLoggerFqcn("f.q.c.n") + .setLevel(Level.DEBUG) + .setMessage(new SimpleMessage("message")) + .setThreadName("threadName") + .setTimeMillis(1) + .build(); + // @formatter:off + final AbstractJacksonLayout layout = + JsonLayout.newBuilder().setCompact(true).build(); + // @formatter:off + return layout.toSerializable(expected); + } + + @Test + void testIncludeNullDelimiterTrue() { + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setCompact(true) + .setIncludeNullDelimiter(true) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertTrue(str.endsWith("\0")); + } + + @Test + void testIncludeNullDelimiterFalse() { + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setCompact(true) + .setIncludeNullDelimiter(false) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertFalse(str.endsWith("\0")); + } + + /** + * @see LOG4J2-2749 + */ + @Test + void testEmptyValuesAreIgnored() { + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setAdditionalFields(new KeyValuePair[] {new KeyValuePair("empty", "${ctx:empty:-}")}) + .setConfiguration(ctx.getConfiguration()) + .build(); + + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertFalse(str.contains("\"empty\""), str); + } + + /** + * @see LOG4J2-3358 + */ + @Test + void jsonLayout_should_substitute_lookups() { + + // Create the layout. + final KeyValuePair[] additionalFields = { + KeyValuePair.newBuilder().setKey("who").setValue("${ctx:WHO}").build() + }; + final JsonLayout layout = JsonLayout.newBuilder() + .setConfiguration(new DefaultConfiguration()) + .setAdditionalFields(additionalFields) + .build(); + + // Create a log event containing `WHO` key in MDC. + final StringMap contextData = ContextDataFactory.createContextData(); + contextData.putValue("WHO", "mduft"); + final LogEvent logEvent = + Log4jLogEvent.newBuilder().setContextData(contextData).build(); + + // Verify the `WHO` key. + final String serializedLogEvent = layout.toSerializable(logEvent); + assertThat(serializedLogEvent, containsString("\"who\" : \"mduft\"")); + } + + private String toPropertySeparator(final boolean compact) { + return compact ? ":" : " : "; + } + + private static class TestClass { + private int value; + + public int getValue() { + return value; + } + + public void setValue(final int value) { + this.value = value; + } + } +} 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 76% 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..38a840e9129 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 @@ -1,23 +1,24 @@ /* * 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.logging.log4j.core.layout; 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 { @@ -32,5 +33,4 @@ protected void log(final int runNumber) { logger.info("Info Message!", val1, val2, val3); logger.info("Info Message!", val1, val2, val3); } - } 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..6dbb93cc898 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.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.layout; + +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.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.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; + +@LoggerContextSource("LOG4J-2195/log4j2.xml") +class Log4j2_2195_Test { + + @Test + 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); + final List events = listAppender.getMessages(); + assertNotNull(events); + assertEquals(1, events.size()); + final 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"); + // + final Layout layout = listAppender.getLayout(); + final PatternLayout pLayout = (PatternLayout) layout; + assertNotNull(pLayout); + final Serializer eventSerializer = pLayout.getEventSerializer(); + assertNotNull(eventSerializer); + // + assertTrue(logEvent.contains("|"), "Missing \"|\""); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java index 0bdaaca4cba..06fb6766e0b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java @@ -1,29 +1,28 @@ /* * 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.logging.log4j.core.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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; import java.util.Collections; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; @@ -78,16 +77,23 @@ static Log4jLogEvent createLogEvent() { .setContextStack(contextStack) // .setThreadName("MyThreadName") // .setSource(source) // - .setTimeMillis(1).build(); + .setTimeMillis(1) + .build(); // validate event? return expected; } @SuppressWarnings("deprecation") - static void assertEqualLogEvents(final LogEvent expected, final LogEvent actual, final boolean includeSource, - final boolean includeContext, final boolean includeStacktrace) { + 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.getContextData() : ContextDataFactory.createContextData(), + actual.getContextData()); assertEquals(includeContext ? expected.getContextMap() : Collections.EMPTY_MAP, actual.getContextMap()); assertEquals(expected.getContextStack(), actual.getContextStack()); assertEquals(expected.getLevel(), actual.getLevel()); @@ -98,8 +104,8 @@ static void assertEqualLogEvents(final LogEvent expected, final LogEvent actual, assertEquals(expected.getTimeMillis(), actual.getTimeMillis()); assertEquals(includeSource ? expected.getSource() : null, actual.getSource()); assertEquals(expected.getThreadName(), actual.getThreadName()); - assertNotNull("original should have an exception", expected.getThrown()); - assertNull("exception should not be serialized", actual.getThrown()); + assertNotNull(expected.getThrown(), "original should have an exception"); + assertNull(actual.getThrown(), "exception should not be serialized"); if (includeStacktrace) { // TODO should compare the rest of the ThrowableProxy assertEquals(expected.getThrownProxy(), actual.getThrownProxy()); } @@ -111,5 +117,4 @@ static void assertEqualLogEvents(final LogEvent expected, final LogEvent actual, assertNotEquals(expected.hashCode(), actual.hashCode()); assertNotEquals(expected, actual); } - } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutDefaultExceptionHandlerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutDefaultExceptionHandlerTest.java new file mode 100644 index 00000000000..14b3176a386 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutDefaultExceptionHandlerTest.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.layout; + +import static org.assertj.core.api.Assertions.assertThat; + +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.NullConfiguration; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.assertj.core.api.AbstractStringAssert; +import org.junit.jupiter.api.Test; + +class PatternLayoutDefaultExceptionHandlerTest { + + private static final Configuration CONFIG = new NullConfiguration(); + + private static final Exception EXCEPTION = new RuntimeException("foo"); + + @Test + void default_exception_handler_should_be_provided() { + final String threadName = Thread.currentThread().getName(); + final String exceptionClassName = EXCEPTION.getClass().getCanonicalName(); + final String exceptionMessage = EXCEPTION.getMessage(); + final String firstLine = String.format("%s%n%s: %s", threadName, exceptionClassName, exceptionMessage); + assertThatPatternEncodes("%t", true).startsWith(firstLine); + } + + @Test + void default_exception_handler_should_be_provided_after_newline() { + final String threadName = Thread.currentThread().getName(); + final String exceptionClassName = EXCEPTION.getClass().getCanonicalName(); + final String exceptionMessage = EXCEPTION.getMessage(); + final String firstLine = String.format("%s%n%s: %s", threadName, exceptionClassName, exceptionMessage); + assertThatPatternEncodes("%t%n", true).startsWith(firstLine); + } + + @Test + void default_exception_handler_should_not_be_provided_if_user_provides_one() { + final String className = EXCEPTION.getStackTrace()[0].getClassName(); + assertThatPatternEncodes("%ex{short.className}", true).isEqualTo(className); + } + + @Test + void default_exception_handler_should_not_be_provided_if_alwaysWriteExceptions_disabled() { + final String threadName = Thread.currentThread().getName(); + assertThatPatternEncodes("%t", false).isEqualTo(threadName); + } + + private static AbstractStringAssert assertThatPatternEncodes( + final String pattern, final boolean alwaysWriteExceptions) { + final Layout layout = PatternLayout.newBuilder() + .setConfiguration(CONFIG) + .setPattern(pattern) + .setAlwaysWriteExceptions(alwaysWriteExceptions) + .build(); + final LogEvent event = Log4jLogEvent.newBuilder().setThrown(EXCEPTION).build(); + return assertThat(layout.toSerializable(event)).as("pattern=`%s`", pattern); + } +} 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..3424fc5d512 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.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; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +/** + * 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") +class PatternLayoutLookupDateTest { + + @Test + 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..b519104e8e6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.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.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; +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.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.junit.jupiter.api.Test; + +/** + * Tests LOG4j2-962. + */ +@LoggerContextSource(value = "log4j2-962.xml", reconfigure = ReconfigurationPolicy.BEFORE_EACH) +class PatternLayoutMainMapLookupTest { + + static { + // Must be set before Log4j writes the header to the appenders. + MainMapLookup.setMainArguments("value0", "value1", "value2"); + } + + @Test + void testFileName(@Named("File") final FileAppender fileApp) { + final String name = fileApp.getFileName(); + assertEquals("target/value0.log", name); + } + + @Test + 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..437e44bc175 --- /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 static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +/** + * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. + */ +@LoggerContextSource("log4j-list.xml") +class PatternLayoutNoLookupDateTest { + + @Test + 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); + assertTrue(string.contains(template), string); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..34d5fb99ff7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java @@ -0,0 +1,803 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Layout; +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.core.lookup.MainMapLookup; +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.Strings; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@UsingAnyThreadContext +class PatternLayoutTest { + 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); + } + + @BeforeAll + static void setupClass() { + ConfigurationFactory.setConfigurationFactory(cf); + final LoggerContext ctx = LoggerContext.getContext(); + ctx.reconfigure(); + } + + LoggerContext ctx = LoggerContext.getContext(); + + Logger root = ctx.getRootLogger(); + + private static class Destination implements ByteBufferDestination { + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[2048]); + + @Override + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + @Override + public ByteBuffer drain(final ByteBuffer buf) { + throw new IllegalStateException("Unexpected message larger than 2048 bytes"); + } + + @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); + } + } + + private void assertToByteArray(final String expectedStr, final PatternLayout layout, final LogEvent event) { + final byte[] result = layout.toByteArray(event); + assertEquals(expectedStr, new String(result)); + } + + private void assertEncode(final String expectedStr, final PatternLayout layout, final LogEvent event) { + final Destination destination = new Destination(); + layout.encode(event, destination); + final ByteBuffer byteBuffer = destination.getByteBuffer(); + byteBuffer.flip(); // set limit to position, position back to zero + assertEquals( + expectedStr, + new String( + byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining())); + } + + @Test + void testEqualsEmptyMarker() { + // replace "[]" with the empty string + 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") // + .setLevel(Level.INFO) // + .setMarker(MarkerManager.getMarker("TestMarker")) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray( + "[org.apache.logging.log4j.core.layout.PatternLayoutTest][TestMarker] Hello, world!", layout, event1); + assertEncode( + "[org.apache.logging.log4j.core.layout.PatternLayoutTest][TestMarker] Hello, world!", layout, event1); + // empty marker + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray("[org.apache.logging.log4j.core.layout.PatternLayoutTest] Hello, world!", layout, event2); + assertEncode("[org.apache.logging.log4j.core.layout.PatternLayoutTest] Hello, world!", layout, event2); + } + + @Test + void testHeaderFooterJavaLookup() { + // % 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() + .setConfiguration(ctx.getConfiguration()) + .setHeader("Header: " + pattern) + .setFooter("Footer: " + pattern) + .build(); + final byte[] header = layout.getHeader(); + assertNotNull(header, "No header"); + final String headerStr = new String(header); + 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(footer, "No footer"); + final String footerStr = new String(footer); + 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); + } + + /** + * Tests LOG4J2-962. + */ + @Test + void testHeaderFooterMainLookup() { + MainMapLookup.setMainArguments("value0", "value1", "value2"); + final PatternLayout layout = PatternLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) + .setHeader("${main:0}") + .setFooter("${main:2}") + .build(); + final byte[] header = layout.getHeader(); + assertNotNull(header, "No header"); + final String headerStr = new String(header); + assertTrue(headerStr.contains("value0"), headerStr); + // + final byte[] footer = layout.getFooter(); + assertNotNull(footer, "No footer"); + final String footerStr = new String(footer); + assertTrue(footerStr.contains("value2"), footerStr); + } + + @Test + void testHeaderFooterThreadContext() { + 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(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) { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern(patternStr) + .setConfiguration(ctx.getConfiguration()) + .build(); + if (useThreadContext) { + ThreadContext.put("key1", "value1"); + ThreadContext.put("key2", "value2"); + } + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello")) + .build(); + assertToByteArray(expectedStr, layout, event); + assertEncode(expectedStr, layout, event); + } + + @Test + void testMdcPattern0() throws Exception { + testMdcPattern("%m : %X", "Hello : {key1=value1, key2=value2}", true); + } + + @Test + void testMdcPattern1() throws Exception { + testMdcPattern("%m : %X", "Hello : {}", false); + } + + @Test + void testMdcPattern2() throws Exception { + testMdcPattern("%m : %X{key1}", "Hello : value1", true); + } + + @Test + void testMdcPattern3() throws Exception { + testMdcPattern("%m : %X{key2}", "Hello : value2", true); + } + + @Test + void testMdcPattern4() throws Exception { + testMdcPattern("%m : %X{key3}", "Hello : ", true); + } + + @Test + void testMdcPattern5() throws Exception { + testMdcPattern("%m : %X{key1}, %X{key2}, %X{key3}", "Hello : value1, value2, ", true); + } + + @Test + void testPatternSelector() { + 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() + .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")) + .setLevel(Level.TRACE) // + .setIncludeLocation(true) + .setMessage(new SimpleMessage("entry")) + .build(); + final String result1 = new FauxLogger().formatEvent(event1, layout); + final String expectPattern1 = + String.format(".*====== PatternLayoutTest.testPatternSelector:\\d+ entry ======%n"); + 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(result2.endsWith(expectSuffix2), "Unexpected result: " + result2); + } + + @Test + void testRegex() { + 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) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray("org/apache/logging/log4j/core/layout/PatternLayoutTest Hello, world!", layout, event); + assertEncode("org/apache/logging/log4j/core/layout/PatternLayoutTest Hello, world!", layout, event); + } + + @Test + void testRegexEmptyMarker() { + // replace "[]" with the empty string + 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") // + .setLevel(Level.INFO) // + .setMarker(MarkerManager.getMarker("TestMarker")) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray( + "[org.apache.logging.log4j.core.layout.PatternLayoutTest][TestMarker] Hello, world!", layout, event1); + assertEncode( + "[org.apache.logging.log4j.core.layout.PatternLayoutTest][TestMarker] Hello, world!", layout, event1); + + // empty marker + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray("[org.apache.logging.log4j.core.layout.PatternLayoutTest] Hello, world!", layout, event2); + assertEncode("[org.apache.logging.log4j.core.layout.PatternLayoutTest] Hello, world!", layout, event2); + } + + @Test + void testEqualsMarkerWithMessageSubstitution() { + // replace "[]" with the empty string + 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") // + .setLevel(Level.INFO) // + .setMarker(MarkerManager.getMarker("TestMarker")) + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + final byte[] result1 = layout.toByteArray(event1); + assertEquals("[org.apache.logging.log4j.core.layout.PatternLayoutTest][TestMarker]", new String(result1)); + // empty marker + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + final byte[] result2 = layout.toByteArray(event2); + assertEquals("[org.apache.logging.log4j.core.layout.PatternLayoutTest][Hello, world!]", new String(result2)); + } + + @Test + void testSpecialChars() { + 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) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + assertToByteArray( + "\\INFO\tHello, world!\n" + "\torg.apache.logging.log4j.core.layout.PatternLayoutTest\r\n" + "\f", + layout, + event); + assertEncode( + "\\INFO\tHello, world!\n" + "\torg.apache.logging.log4j.core.layout.PatternLayoutTest\r\n" + "\f", + layout, + event); + } + + @Test + void testUnixTime() { + 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) // + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final byte[] result1 = layout.toByteArray(event1); + assertEquals(event1.getTimeMillis() / 1000 + " Hello, world 1!", new String(result1)); + // System.out.println("event1=" + event1.getTimeMillis() / 1000); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world 2!")) + .build(); + final byte[] result2 = layout.toByteArray(event2); + assertEquals(event2.getTimeMillis() / 1000 + " Hello, world 2!", new String(result2)); + // System.out.println("event2=" + event2.getTimeMillis() / 1000); + } + + @SuppressWarnings("unused") + private void testUnixTime(final String pattern) { + 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) // + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final byte[] result1 = layout.toByteArray(event1); + assertEquals(event1.getTimeMillis() + " Hello, world 1!", new String(result1)); + // System.out.println("event1=" + event1.getMillis()); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world 2!")) + .build(); + final byte[] result2 = layout.toByteArray(event2); + assertEquals(event2.getTimeMillis() + " Hello, world 2!", new String(result2)); + // System.out.println("event2=" + event2.getMillis()); + } + + @Test + void testUnixTimeMillis() { + 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) // + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final byte[] result1 = layout.toByteArray(event1); + assertEquals(event1.getTimeMillis() + " Hello, world 1!", new String(result1)); + // System.out.println("event1=" + event1.getTimeMillis()); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world 2!")) + .build(); + final byte[] result2 = layout.toByteArray(event2); + assertEquals(event2.getTimeMillis() + " Hello, world 2!", new String(result2)); + // System.out.println("event2=" + event2.getTimeMillis()); + } + + @Test + void testUsePlatformDefaultIfNoCharset() { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern("%m") + .setConfiguration(ctx.getConfiguration()) + .build(); + assertEquals(Charset.defaultCharset(), layout.getCharset()); + } + + @Test + void testUseSpecifiedCharsetIfExists() { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern("%m") + .setConfiguration(ctx.getConfiguration()) + .setCharset(StandardCharsets.UTF_8) + .build(); + assertEquals(StandardCharsets.UTF_8, layout.getCharset()); + } + + @Test + void testLoggerNameTruncationByRetainingPartsFromEnd() { + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals( + this.getClass() + .getName() + .substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", + new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(0, this.getClass().getName().lastIndexOf(".")); + name = name.substring(0, name.lastIndexOf(".")); + assertEquals( + this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1)); + } + } + + @Test + void testCallersFqcnTruncationByRetainingPartsFromEnd() { + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByRetainingPartsFromEnd", + this.getClass().getCanonicalName() + ".java", + 440)) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals( + this.getClass() + .getName() + .substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", + new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByRetainingPartsFromEnd", + this.getClass().getCanonicalName() + ".java", + 440)) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(0, this.getClass().getName().lastIndexOf(".")); + name = name.substring(0, name.lastIndexOf(".")); + assertEquals( + this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByRetainingPartsFromEnd", + this.getClass().getCanonicalName() + ".java", + 440)) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByRetainingPartsFromEnd", + this.getClass().getCanonicalName() + ".java", + 440)) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals( + this.getClass() + .getName() + .substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", + new String(result1)); + } + } + + @Test + void testLoggerNameTruncationByDroppingPartsFromFront() { + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + final String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1)); + } + } + + @Test + void testCallersFqcnTruncationByDroppingPartsFromFront() { + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByDroppingPartsFromFront", + this.getClass().getCanonicalName() + ".java", + 546)) + .build(); + final String result1 = layout.toSerializable(event1); + final String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByDroppingPartsFromFront", + this.getClass().getCanonicalName() + ".java", + 546)) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByDroppingPartsFromFront", + this.getClass().getCanonicalName() + ".java", + 546)) + .build(); + final String result1 = layout.toSerializable(event1); + String name = this.getClass() + .getName() + .substring(this.getClass().getName().indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + name = name.substring(name.indexOf(".") + 1); + assertEquals(name + " Hello, world 1!", new String(result1)); + } + { + 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) + .setMessage(new SimpleMessage("Hello, world 1!")) + .setSource(new StackTraceElement( + this.getClass().getName(), + "testCallersFqcnTruncationByDroppingPartsFromFront", + this.getClass().getCanonicalName() + ".java", + 546)) + .build(); + final String result1 = layout.toSerializable(event1); + assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1)); + } + } +} 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..9712819fc34 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.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.layout; + +import static org.junit.jupiter.api.Assertions.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.jupiter.api.Test; + +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 + void testMarkerPatternSelector() { + 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() + .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:58 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 + void testLevelPatternSelector() { + 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:90 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..87157ec68bf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java @@ -0,0 +1,795 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.net.InetAddress; +import java.net.UnknownHostException; +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.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.Node; +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.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.message.StructuredDataCollectionMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.ProcessIdUtil; +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; + +@UsingAnyThreadContext +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(); + + @BeforeAll + static void setupClass() { + StatusLogger.getLogger().setLevel(Level.OFF); + ConfigurationFactory.setConfigurationFactory(cf); + final LoggerContext ctx = LoggerContext.getContext(); + ctx.reconfigure(); + } + + @AfterAll + static void cleanupClass() { + ConfigurationFactory.removeConfigurationFactory(cf); + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + void testLayout() { + 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 + void testCollection() { + 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); + + final List list = appender.getMessages(); + final 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 + void testEscape() { + 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 + void testException() { + 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 + void testMDCLoggerFields() { + 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.isEmpty(), "Not enough list entries"); + assertTrue(list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields"), "No class/method"); + + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + 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.isEmpty(), "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 + 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.isEmpty(), "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 + 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.isEmpty(), "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 + 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.isEmpty(), "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.isEmpty(), "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 testLayoutBuilderDefaultValues() { + final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().build(); + checkDefaultValues(layout); + + final PluginManager manager = new PluginManager(Node.CATEGORY); + manager.collectPlugins(); + final Object obj = new PluginBuilder(manager.getPluginType("Rfc5424Layout")) + .withConfigurationNode(new Node()) + .withConfiguration(new DefaultConfiguration()) + .build(); + assertInstanceOf(Rfc5424Layout.class, obj); + 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()); + assertTrue(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(final String eid) { + final AbstractStringLayout layout = + new Rfc5424Layout.Rfc5424LayoutBuilder().setEin(eid).build(); + + assertNotNull(layout); + } + + @ParameterizedTest + @ValueSource(strings = {"abc", "someEid", "-1"}) + void testLayoutBuilderInvalidEids(final String eid) { + final AbstractStringLayout layout = + new Rfc5424Layout.Rfc5424LayoutBuilder().setEin(eid).build(); + + assertNull(layout); + } + + @Test + void testFQDN() throws UnknownHostException { + final String fqdn = InetAddress.getLocalHost().getCanonicalHostName(); + final Rfc5424Layout layout = Rfc5424Layout.newBuilder().build(); + assertThat(layout.getLocalHostName()).isEqualTo(fqdn); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java new file mode 100644 index 00000000000..de18512f1dd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.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.core.layout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +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.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.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.apache.logging.log4j.test.junit.ThreadContextRule; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +/** + * + */ +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(); + + @Rule + public final ThreadContextRule threadContextRule = new ThreadContextRule(); + + @BeforeClass + public static void setupClass() { + 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() { + 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(); + assertFalse(data.isEmpty()); + int i = 0; + for (final byte[] item : data) { + assertEquals( + "Incorrect event", expected[i], SerialUtil.deserialize(item).toString()); + ++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); + try (final ObjectInputStream ois = SerialUtil.getObjectInputStream(fis)) { + final LogEvent event = (LogEvent) ois.readObject(); + assertNotNull(event); + } + } +} 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 87% 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 index acc8da7be35..1147f0a6b5c 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.layout; 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..6045f2a49d6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.jupiter.api.Assertions.assertEquals; + +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +/** + * Tests the {@code TextEncoderHelper} class. + */ +class StringBuilderEncoderTest { + + @Test + void testEncodeText_TextFitCharBuff_BytesFitByteBuff() { + 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 + void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff() { + 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 + void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff_MultiplePasses() { + 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 + void testEncodeText_TextDoesntFitCharBuff_BytesFitByteBuff() { + 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 + void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesFitByteBuff() { + 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 + void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesFitByteBuff() { + 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 + void testEncodeText_TextDoesntFitCharBuff_BytesDontFitByteBuff() { + 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 + void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesDontFitByteBuff() { + 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 + void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() { + 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 + void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesDontFitByteBuff() { + 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 + void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() { + 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 + void testCopyCopiesAllDataIfSuffientRemainingSpace() { + 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 + void testCopyUpToRemainingSpace() { + 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 + void testCopyDoesNotWriteBeyondStringText() { + 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 + void testCopyStartsAtBufferPosition() { + 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 + void testEncode_ALotWithoutErrors() { + 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-test/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 new file mode 100644 index 00000000000..4ae6dd14b98 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.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.layout; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.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.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@UsingAnyThreadContext +class SyslogLayoutTest { + LoggerContext ctx = LoggerContext.getContext(); + Logger root = ctx.getRootLogger(); + + private static final String line1 = "starting mdc pattern test"; + private static final String line2 = "empty mdc"; + private static final String line3 = "filled mdc"; + private static final String line4 = + "Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete"; + + static ConfigurationFactory cf = new BasicConfigurationFactory(); + + @BeforeAll + static void setupClass() { + ConfigurationFactory.setConfigurationFactory(cf); + final LoggerContext ctx = LoggerContext.getContext(); + ctx.reconfigure(); + } + + @AfterAll + static void cleanupClass() { + ConfigurationFactory.removeConfigurationFactory(cf); + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + void testLayout() { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up appender + // @formatter:off + final SyslogLayout layout = SyslogLayout.newBuilder() + .setFacility(Facility.LOCAL0) + .setIncludeNewLine(true) + .build(); + // @formatter:on + // ConsoleAppender appender = new ConsoleAppender("Console", layout); + 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"); + + ThreadContext.put("key1", "value1"); + ThreadContext.put("key2", "value2"); + + root.debug("filled mdc"); + + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + 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); + + appender.stop(); + + final 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)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java index 4ee8f1eac65..0f246d28389 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java @@ -1,56 +1,59 @@ /* * 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.logging.log4j.core.layout; +import static org.hamcrest.MatcherAssert.assertThat; +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 static org.xmlunit.matchers.HasXPathMatcher.hasXPath; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; 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.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.Layouts; 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.apache.logging.log4j.test.junit.ThreadContextRule; 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}. */ @@ -61,7 +64,7 @@ public class XmlLayoutTest { private static final String markerTag = ""; @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); + public final ThreadContextRule threadContextRule = new ThreadContextRule(); @AfterClass public static void cleanupClass() { @@ -102,7 +105,11 @@ private void checkElement(final String key, final String value, final boolean co assertTrue(str, str.contains(String.format("", key, value))); } - private void checkElementName(final String name, final boolean compact, final String str, final boolean withAttributes, + 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 @@ -127,8 +134,12 @@ private void checkElementNameAbsent(final String name, final boolean compact, fi * @throws JsonParseException * @throws JsonMappingException */ - private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean includeContext, final boolean includeStacktrace) throws IOException, - JsonParseException, 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) @@ -141,8 +152,8 @@ private void testAllFeatures(final boolean includeSource, final boolean compact, 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")); + assertEquals(str, includeSource, str.contains(" appenders = this.rootLogger.getAppenders(); for (final Appender appender : appenders.values()) { this.rootLogger.removeAppender(appender); @@ -272,8 +283,8 @@ public void testLayout() throws Exception { 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("")); + assertEquals("Incorrect header: " + string, "", string); + assertEquals("Incorrect footer", "", list.get(list.size() - 1)); this.checkContains("loggerFqcn=\"" + AbstractLogger.class.getName() + "\"", list); this.checkContains("level=\"DEBUG\"", list); this.checkContains(">starting mdc pattern test", list); @@ -304,26 +315,28 @@ public void testLayoutLoggerName() { .setLevel(Level.DEBUG) // .setMessage(new SimpleMessage("M")) // .setThreadName("threadName") // - .setTimeMillis(1).build(); + .setTimeMillis(1) + .build(); final String str = layout.toSerializable(event); assertTrue(str, str.contains("loggerName=\"a.B\"")); } @Test - public void testAdditionalFields() throws Exception { + public void testAdditionalFields() { final AbstractJacksonLayout layout = XmlLayout.newBuilder() .setLocationInfo(false) .setProperties(false) .setIncludeStacktrace(false) .setAdditionalFields(new KeyValuePair[] { - new KeyValuePair("KEY1", "VALUE1"), - new KeyValuePair("KEY2", "${java:runtime}"), }) + 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() + "")); + + assertThat(str, hasXPath("//KEY1[text()='VALUE1']")); + assertThat(str, hasXPath("//KEY2[text()='" + new JavaLookup().getRuntime() + "']")); } @Test @@ -342,13 +355,13 @@ public void testExcludeStacktrace() throws Exception { } @Test - public void testStacktraceAsString() throws Exception { + public void testStacktraceAsString() { final String str = prepareXMLForStacktraceTests(true); assertTrue(str, str.contains("java.lang.NullPointerException")); } @Test - public void testStacktraceAsNonString() throws Exception { + public void testStacktraceAsNonString() { final String str = prepareXMLForStacktraceTests(false); assertTrue(str, str.contains(" list) { + final String trimedLine = list.get(lineIndex).trim(); + assertEquals(expected, trimedLine, "Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine)); + } + + private void checkContains(final String expected, final List list) { + for (final String string : list) { + final String trimedLine = string.trim(); + if (trimedLine.equals(expected)) { + return; + } + } + fail("Cannot find " + expected + " in " + list); + } + + private void checkMapEntry(final String key, final String value, final boolean compact, final String str) { + // "name":"value" + // final String expected = String.format("- key: \"%s\"\n value: \"%s\"", key, value); + final String expected = String.format("%s: \"%s\"", key, value); + assertTrue(str.contains(expected), "Cannot find " + expected + " in " + str); + } + + private void checkProperty( + final String key, final String value, final boolean compact, final String str, final boolean isValue) { + final String propSep = this.toPropertySeparator(compact, isValue); + // {"key":"MDC.B","value":"B_Value"} + final String expected = String.format("%s%s\"%s\"", key, propSep, value); + assertTrue(str.contains(expected), "Cannot find " + expected + " in " + str); + } + + private void checkPropertyName(final String name, final boolean compact, final String str, final boolean isValue) { + final String propSep = this.toPropertySeparator(compact, isValue); + assertTrue(str.contains(name + propSep), str); + } + + private void checkPropertyNameAbsent( + final String name, final boolean compact, final String str, final boolean isValue) { + final String propSep = this.toPropertySeparator(compact, isValue); + assertFalse(str.contains(name + propSep), str); + } + + private void testAllFeatures( + final boolean includeSource, + final boolean compact, + final boolean eventEol, + final boolean includeContext, + final boolean contextMapAslist, + final boolean includeStacktrace) + throws Exception { + final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); + final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setLocationInfo(includeSource) + .setProperties(includeContext) + .setIncludeStacktrace(includeStacktrace) + .setCharset(StandardCharsets.UTF_8) + .build(); + final String str = layout.toSerializable(expected); + // System.out.println(str); + // Just check for \n since \r might or might not be there. + assertEquals(!compact || eventEol, str.contains("\n"), str); + assertEquals(includeSource, str.contains("source"), str); + assertEquals(includeContext, str.contains("contextMap"), str); + final Log4jLogEvent actual = new Log4jYamlObjectMapper(contextMapAslist, includeStacktrace, false) + .readValue(str, Log4jLogEvent.class); + LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, includeContext, includeStacktrace); + if (includeContext) { + this.checkMapEntry("MDC.A", "A_Value", compact, str); + this.checkMapEntry("MDC.B", "B_Value", compact, str); + } + // + assertNull(actual.getThrown()); + // make sure the names we want are used + this.checkPropertyName("instant", compact, str, false); + this.checkPropertyName("thread", compact, str, true); // and not threadName + this.checkPropertyName("level", compact, str, true); + this.checkPropertyName("loggerName", compact, str, true); + this.checkPropertyName("marker", compact, str, false); + this.checkPropertyName("name", compact, str, true); + this.checkPropertyName("parents", compact, str, false); + this.checkPropertyName("message", compact, str, true); + this.checkPropertyName("thrown", compact, str, false); + this.checkPropertyName("cause", compact, str, false); + this.checkPropertyName("commonElementCount", compact, str, true); + this.checkPropertyName("localizedMessage", compact, str, true); + if (includeStacktrace) { + this.checkPropertyName("extendedStackTrace", compact, str, false); + this.checkPropertyName("class", compact, str, true); + this.checkPropertyName("method", compact, str, true); + this.checkPropertyName("file", compact, str, true); + this.checkPropertyName("line", compact, str, true); + this.checkPropertyName("exact", compact, str, true); + this.checkPropertyName("location", compact, str, true); + this.checkPropertyName("version", compact, str, true); + } else { + this.checkPropertyNameAbsent("extendedStackTrace", compact, str, false); + } + this.checkPropertyName("suppressed", compact, str, false); + this.checkPropertyName("loggerFqcn", compact, str, true); + this.checkPropertyName("endOfBatch", compact, str, true); + if (includeContext) { + this.checkPropertyName("contextMap", compact, str, false); + } else { + this.checkPropertyNameAbsent("contextMap", compact, str, false); + } + this.checkPropertyName("contextStack", compact, str, false); + if (includeSource) { + this.checkPropertyName("source", compact, str, false); + } else { + this.checkPropertyNameAbsent("source", compact, str, false); + } + // check some attrs + this.checkProperty("loggerFqcn", "f.q.c.n", compact, str, true); + this.checkProperty("loggerName", "a.B", compact, str, true); + } + + @Test + void testContentType() { + final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout(); + assertEquals("application/yaml; charset=UTF-8", layout.getContentType()); + } + + @Test + void testDefaultCharset() { + final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout(); + assertEquals(StandardCharsets.UTF_8, layout.getCharset()); + } + + @Test + void testEscapeLayout() { + 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 AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setLocationInfo(true) + .setProperties(true) + .setIncludeStacktrace(true) + .setConfiguration(configuration) + .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("Here is a quote ' and then a double quote \""); + + appender.stop(); + + final List list = appender.getMessages(); + + this.checkAt("---", 0, 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 + void testLayout() { + 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 AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, "[[", "]]", null, true); + 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.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 + void testLayoutLoggerName() throws Exception { + final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setIncludeStacktrace(true) + .setCharset(StandardCharsets.UTF_8) + .build(); + 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(); + final String str = layout.toSerializable(expected); + assertTrue(str.contains("loggerName: \"a.B\""), str); + final Log4jLogEvent actual = new Log4jYamlObjectMapper(false, true, false).readValue(str, Log4jLogEvent.class); + assertEquals(expected.getLoggerName(), actual.getLoggerName()); + assertEquals(expected, actual); + } + + @Test + void testAdditionalFields() throws Exception { + final AbstractJacksonLayout layout = YamlLayout.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()); + final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + final JsonNode node = mapper.readTree(str); + assertThat(getJSONChildNodeValueAsText(node, "KEY1"), equalTo("VALUE1")); + assertThat(getJSONChildNodeValueAsText(node, "KEY2"), equalTo(new JavaLookup().getRuntime())); + } + + private String getJSONChildNodeValueAsText(final JsonNode parentNode, final String key) { + final JsonNode childNode = parentNode.get(key); + if (childNode != null) { + return childNode.asText(); + } else { + return Strings.EMPTY; + } + } + + @Test + void testLocationOffCompactOffMdcOff() throws Exception { + this.testAllFeatures(false, false, false, false, false, true); + } + + @Test + void testLocationOnCompactOffEventEolOffMdcOn() throws Exception { + this.testAllFeatures(true, false, false, true, false, true); + } + + @Test + void testExcludeStacktrace() throws Exception { + this.testAllFeatures(false, false, false, false, false, false); + } + + @Test + void testStacktraceAsString() { + final String str = prepareYAMLForStacktraceTests(true); + assertTrue(str.contains("extendedStackTrace: \"java.lang.NullPointerException"), str); + } + + @Test + void testStacktraceAsNonString() { + final String str = prepareYAMLForStacktraceTests(false); + assertTrue(str.contains("extendedStackTrace:\n - "), str); + } + + private String prepareYAMLForStacktraceTests(final boolean stacktraceAsString) { + final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); + // @formatter:off + final AbstractJacksonLayout layout = YamlLayout.newBuilder() + .setIncludeStacktrace(true) + .setStacktraceAsString(stacktraceAsString) + .build(); + // @formatter:off + return layout.toSerializable(expected); + } + + @Test + void testIncludeNullDelimiterTrue() { + final AbstractJacksonLayout layout = + YamlLayout.newBuilder().setIncludeNullDelimiter(true).build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertTrue(str.endsWith("\0")); + } + + @Test + void testIncludeNullDelimiterFalse() { + final AbstractJacksonLayout layout = + YamlLayout.newBuilder().setIncludeNullDelimiter(false).build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertFalse(str.endsWith("\0")); + } + + private String toPropertySeparator(final boolean compact, final boolean value) { + return value ? ": " : ":"; + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..f318f476fdf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/log4j-customLevels.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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..d12ff39a8b0 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +class CaseLookupTest { + + @Test + 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..d294f2fd27e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ContextMapLookupTest.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.lookup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +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; + +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() { + 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..b971317b511 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.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.lookup; + +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.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import org.apache.logging.log4j.core.LogEvent; +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; + +class DateLookupTest { + + @Test + 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 + 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"}) + void testInvalidKey(final 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-test/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 new file mode 100644 index 00000000000..4f119566d0f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.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.lookup; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class EnvironmentLookupTest { + + @Test + void testLookup() { + final StrLookup lookup = new EnvironmentLookup(); + String value = lookup.lookup("PATH"); + assertNotNull(value); + value = lookup.lookup("BadKey"); + assertNull(value); + } +} 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..8b85421ce7b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.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.lookup; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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; + +/** + * Tests {@link MarkerLookup}. + * + * @since 2.4 + */ +class EventLookupTest { + + private static final String ABSENT_MARKER_NAME = "NONE"; + private final String markerName = "EventLookupTest"; + private final StrLookup strLookup = new EventLookup(); + + @Test + 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 + void testLookupEventMessage() { + final 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 + void testLookupEventLevel() { + final 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 + void testLookupEventTimestamp() { + final String msg = "Hello, world!"; + final 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 + void testLookupEventLogger() { + final 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 + void testLookupEventThreadName() { + final 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..1301ac8a666 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; + +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.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerContextAware; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.ExternalResource; +import org.junit.rules.RuleChain; +import org.junitpioneer.jupiter.Issue; + +/** + * Tests {@link Interpolator}. + */ +public class InterpolatorTest { + public static final String TEST_LOOKUP = "interpolator_test"; + + 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 final RuleChain RULES = RuleChain.outerRule(new ExternalResource() { + @Override + protected void before() { + System.setProperty(TESTKEY, TESTVAL); + System.setProperty(TESTKEY2, TESTVAL); + System.setProperty("log4j2.enableJndiLookup", "true"); + } + + @Override + protected void after() { + System.clearProperty(TESTKEY); + System.clearProperty(TESTKEY2); + System.clearProperty("log4j2.enableJndiLookup"); + } + }) + .around(new JndiRule( + JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME)); + + @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); + 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"); + } + + @Test + public void testInterpolatorMapMessageWithNoPrefix() { + final HashMap configProperties = new HashMap<>(); + configProperties.put("key", "configProperties"); + final Interpolator interpolator = new Interpolator(configProperties); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + final 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() { + final Interpolator interpolator = new Interpolator(Collections.emptyMap()); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + final 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"); + final Interpolator interpolator = new Interpolator(configProperties); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + final 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")); + } + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/2309") + public void testContextAndConfigurationPropagation() { + final Interpolator interpolator = new Interpolator(); + assertThat(getConfiguration(interpolator)).isNull(); + assertThat(getLoggerContext(interpolator)).isNull(); + + final Lookup lookup = (Lookup) interpolator.getStrLookupMap().get(TEST_LOOKUP); + assertThat(lookup) + .isNotNull() + .as("Configuration and logger context are null") + .extracting(Lookup::getConfiguration, Lookup::getLoggerContext) + .containsExactly(null, null); + + // Evaluation does not throw, even if config and context are null. + assertDoesNotThrow(() -> interpolator.evaluate(null, TEST_LOOKUP + ":any_key")); + + final Configuration config = mock(Configuration.class); + interpolator.setConfiguration(config); + assertThat(getConfiguration(interpolator)).isEqualTo(config); + assertThat(lookup.getConfiguration()).as("Configuration propagates").isEqualTo(config); + + final LoggerContext context = mock(LoggerContext.class); + interpolator.setLoggerContext(context); + assertThat(getLoggerContext(interpolator)).isEqualTo(context); + assertThat(lookup.getLoggerContext()).as("Logger context propagates").isEqualTo(context); + } + + // Used in tests from other packages + public static Configuration getConfiguration(final Interpolator interpolator) { + return interpolator.configuration; + } + + // Used in tests from other packages + public static LoggerContext getLoggerContext(final Interpolator interpolator) { + return interpolator.loggerContext.get(); + } + + @Plugin(name = TEST_LOOKUP, category = StrLookup.CATEGORY) + public static class Lookup extends AbstractConfigurationAwareLookup implements LoggerContextAware { + + private LoggerContext loggerContext; + + public Configuration getConfiguration() { + return configuration; + } + + public LoggerContext getLoggerContext() { + return loggerContext; + } + + @Override + public void setLoggerContext(final LoggerContext loggerContext) { + this.loggerContext = loggerContext; + } + + @Override + public String lookup(final LogEvent event, final String key) { + return null; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.java new file mode 100644 index 00000000000..6827b0035c6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiDisabledLookupTest.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.lookup; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * JndiDisabledLookupTest + * + * Verifies the Lookups are disabled without the log4j2.enableJndiLookup property set to true. + */ +class JndiDisabledLookupTest { + + @Test + void testLookup() { + assertThrows(IllegalStateException.class, JndiLookup::new); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiExploit.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiExploit.java new file mode 100644 index 00000000000..ed34340c86e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiExploit.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 static org.junit.jupiter.api.Assertions.fail; + +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.spi.ObjectFactory; + +/** + * Test LDAP object + */ +public class JndiExploit implements ObjectFactory { + @Override + public Object getObjectInstance( + final Object obj, final Name name, final Context nameCtx, final Hashtable environment) { + fail("getObjectInstance must not be allowed"); + return null; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java index c2e34e34489..b68fb2eb8ae 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiLookupTest.java @@ -1,32 +1,33 @@ /* * 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.logging.log4j.core.lookup; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; - -import org.apache.logging.log4j.junit.JndiRule; +import org.apache.logging.log4j.core.test.junit.JndiRule; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.*; - /** * JndiLookupTest */ @@ -42,6 +43,11 @@ public class JndiLookupTest { @Rule public JndiRule jndiRule = new JndiRule(createBindings()); + @BeforeClass + public static void beforeClass() { + System.setProperty("log4j2.enableJndiLookup", "true"); + } + private Map createBindings() { final Map map = new HashMap<>(); map.put(JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME); @@ -65,7 +71,7 @@ public void testLookup() { } @Test - public void testNonStringLookup() throws Exception { + public void testNonStringLookup() { // LOG4J2-1310 final StrLookup lookup = new JndiLookup(); final String integralValue = lookup.lookup(TEST_INTEGRAL_NAME); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java new file mode 100644 index 00000000000..9fc683d1aeb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/JndiRestrictedLookupTest.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.fail; + +import java.io.Serializable; +import javax.naming.Context; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.StringRefAddr; +import org.apache.logging.log4j.message.Message; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.zapodot.junit.ldap.EmbeddedLdapRule; +import org.zapodot.junit.ldap.EmbeddedLdapRuleBuilder; + +/** + * JndiLookupTest + */ +public class JndiRestrictedLookupTest { + + private static final String LDAP_URL = "ldap://127.0.0.1:"; + private static final String RESOURCE = "JndiExploit"; + private static final String TEST_STRING = "TestString"; + private static final String TEST_MESSAGE = "TestMessage"; + private static final String LEVEL = "TestLevel"; + private static final String DOMAIN_DSN = "dc=apache,dc=org"; + private static final String DOMAIN = "apache.org"; + + @Rule + public EmbeddedLdapRule embeddedLdapRule = EmbeddedLdapRuleBuilder.newInstance() + .usingDomainDsn(DOMAIN_DSN) + .importingLdifs("JndiRestrictedLookup.ldif") + .build(); + + @BeforeClass + public static void beforeClass() { + System.setProperty("log4j2.enableJndiLookup", "true"); + } + + @Test + @SuppressWarnings("BanJNDI") + public void testBadUriLookup() throws Exception { + final int port = embeddedLdapRule.embeddedServerPort(); + final Context context = embeddedLdapRule.context(); + context.bind("cn=" + RESOURCE + "," + DOMAIN_DSN, new Fruit("Test Message")); + final StrLookup lookup = new JndiLookup(); + final String result = lookup.lookup( + LDAP_URL + port + "/" + "cn=" + RESOURCE + "," + DOMAIN_DSN + "?Type=A Type&Name=1100110&Char=!"); + if (result != null) { + fail("Lookup returned an object"); + } + } + + @Test + @SuppressWarnings("BanJNDI") + public void testReferenceLookup() throws Exception { + final int port = embeddedLdapRule.embeddedServerPort(); + final Context context = embeddedLdapRule.context(); + context.bind("cn=" + RESOURCE + "," + DOMAIN_DSN, new Fruit("Test Message")); + final StrLookup lookup = new JndiLookup(); + final String result = lookup.lookup(LDAP_URL + port + "/" + "cn=" + RESOURCE + "," + DOMAIN_DSN); + if (result != null) { + fail("Lookup returned an object"); + } + } + + @Test + @SuppressWarnings("BanJNDI") + public void testSerializableLookup() throws Exception { + final int port = embeddedLdapRule.embeddedServerPort(); + final Context context = embeddedLdapRule.context(); + context.bind("cn=" + TEST_STRING + "," + DOMAIN_DSN, "Test Message"); + final StrLookup lookup = new JndiLookup(); + final String result = lookup.lookup(LDAP_URL + port + "/" + "cn=" + TEST_STRING + "," + DOMAIN_DSN); + if (result != null) { + fail("LDAP is enabled"); + } + } + + @Test + @SuppressWarnings("BanJNDI") + public void testBadSerializableLookup() throws Exception { + final int port = embeddedLdapRule.embeddedServerPort(); + final Context context = embeddedLdapRule.context(); + context.bind("cn=" + TEST_MESSAGE + "," + DOMAIN_DSN, new SerializableMessage("Test Message")); + final StrLookup lookup = new JndiLookup(); + final String result = lookup.lookup(LDAP_URL + port + "/" + "cn=" + TEST_MESSAGE + "," + DOMAIN_DSN); + if (result != null) { + fail("Lookup returned an object"); + } + } + + @Test + public void testDnsLookup() { + final StrLookup lookup = new JndiLookup(); + final String result = lookup.lookup("dns:/" + DOMAIN); + if (result != null) { + fail("No DNS data returned"); + } + } + + static class Fruit implements Referenceable { + String fruit; + + public Fruit(final String f) { + fruit = f; + } + + public Reference getReference() { + + return new Reference( + Fruit.class.getName(), + new StringRefAddr("fruit", fruit), + JndiExploit.class.getName(), + null); // factory location + } + + public String toString() { + return fruit; + } + } + + static class SerializableMessage implements Serializable, Message { + private final String message; + + SerializableMessage(final String message) { + this.message = message; + } + + @Override + public String getFormattedMessage() { + return message; + } + + @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/lookup/Log4jLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java new file mode 100644 index 00000000000..38ef1f08154 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.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 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.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +import java.io.File; +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.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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class Log4jLookupTest { + + private static final File EXPECT = new File(System.getProperty("user.home"), "/a/b/c/d/e/log4j2.xml"); + + @Mock + private LoggerContext mockCtx; + + @Mock + private Configuration config; + + @Mock + private ConfigurationSource configSrc; + + @BeforeEach + void setup() { + ContextAnchor.THREAD_CONTEXT.set(mockCtx); + given(config.getConfigurationSource()).willReturn(configSrc); + given(configSrc.getFile()).willReturn(EXPECT); + } + + @AfterEach + void cleanup() { + ContextAnchor.THREAD_CONTEXT.set(null); + } + + @Test + void lookupConfigLocation() { + final StrLookup log4jLookup = new Log4jLookup(); + ((ConfigurationAware) log4jLookup).setConfiguration(config); + final String value = log4jLookup.lookup(KEY_CONFIG_LOCATION); + assertEquals(EXPECT.getAbsolutePath(), value); + } + + @Test + void lookupConfigParentLocation() { + final StrLookup log4jLookup = new Log4jLookup(); + ((ConfigurationAware) log4jLookup).setConfiguration(config); + final String value = log4jLookup.lookup(KEY_CONFIG_PARENT_LOCATION); + assertEquals(EXPECT.getParentFile().getAbsolutePath(), value); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..6a445ba08e8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.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 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.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +import java.io.File; +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.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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class Log4jLookupWithSpacesTest { + + private static final File EXPECT = + new File(System.getProperty("user.home"), "/a a/b b/c c/d d/e e/log4j2 file.xml"); + + @Mock + private LoggerContext mockCtx; + + @Mock + private Configuration config; + + @Mock + private ConfigurationSource configSrc; + + @BeforeEach + void setup() { + ContextAnchor.THREAD_CONTEXT.set(mockCtx); + given(config.getConfigurationSource()).willReturn(configSrc); + given(configSrc.getFile()).willReturn(EXPECT); + } + + @AfterEach + void cleanup() { + ContextAnchor.THREAD_CONTEXT.set(null); + } + + @Test + void lookupConfigLocation_withSpaces() { + final StrLookup log4jLookup = new Log4jLookup(); + ((ConfigurationAware) log4jLookup).setConfiguration(config); + final String value = log4jLookup.lookup(KEY_CONFIG_LOCATION); + assertEquals( + new File(System.getProperty("user.home"), "/a a/b b/c c/d d/e e/log4j2 file.xml").getAbsolutePath(), + value); + } + + @Test + void lookupConfigParentLocation_withSpaces() { + final StrLookup log4jLookup = new Log4jLookup(); + ((ConfigurationAware) log4jLookup).setConfiguration(config); + final String value = log4jLookup.lookup(KEY_CONFIG_PARENT_LOCATION); + assertEquals(new File(System.getProperty("user.home"), "/a a/b b/c c/d d/e e").getAbsolutePath(), value); + } +} 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..ecce72d45e7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.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.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-test/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 new file mode 100644 index 00000000000..37ada46e7d7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.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.lookup; + +import java.util.Map; +import org.apache.logging.log4j.core.LogEvent; + +/** + * 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 { + + public static final MainInputArgumentsMapLookup SINGLETON_STACK; + + static { + final Map allStackTraces = Thread.getAllStackTraces(); + final String[] args = null; + for (final Map.Entry entry : allStackTraces.entrySet()) { + final StackTraceElement[] stackTraceElements = entry.getValue(); + // 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 vendor dependent. Oracle seems to + // use 1. + // We are left to look for "main" at the top of the stack + if (stackTraceElements != null) { + final int frame0 = stackTraceElements.length - 1; + if ("main".equals(stackTraceElements[frame0].getMethodName())) { + // We could further validate the main is a public static void method that takes a String[], if not, + // look at the other threads. + // + // How do we get the main args from the stack with the debug API? + // Must we be started in debug mode? Seems like it. + } + } + } + SINGLETON_STACK = new MainInputArgumentsMapLookup(MapLookup.toMap(args)); + } + + 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; + } + final 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..bde633be44d --- /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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * Tests MainLookup. + */ +class MainLookupTest { + + @Test + void testMainArgs() { + MainMapLookup.setMainArguments("--file", "foo.txt", "--verbose", "-x", "bar"); + final 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}"; + final Map properties = new HashMap<>(); + properties.put("key", "value"); + properties.put("bar", "default_bar_value"); + final Interpolator lookup = new Interpolator(properties); + final StrSubstitutor substitutor = new StrSubstitutor(lookup); + final String replacedValue = substitutor.replace(null, str); + final 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..2ad36d5aca9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +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; + +/** + * Tests {@link MapLookup}. + */ +class MapLookupTest { + + @Test + void testEmptyMap() { + final MapLookup lookup = new MapLookup(new HashMap()); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + } + + @Test + 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 + void testNullMap() { + final MapLookup lookup = new MapLookup(); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + } + + @Test + 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 + 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 + 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 + 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 + 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..9ceaab37597 --- /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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +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; + +/** + * Tests {@link MarkerLookup} with a configuration file. + * + * @since 2.4 + */ +@LoggerContextSource("log4j-marker-lookup.yaml") +@Tag("yaml") +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 + 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-test/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 new file mode 100644 index 00000000000..35d4c4cf34d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.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.lookup; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.Test; + +/** + * Tests {@link MarkerLookup}. + * + * @since 2.4 + */ +class MarkerLookupTest { + + private static final String ABSENT_MARKER_NAME = "NONE"; + private final String markerName = "MarkerLookupTest"; + private final StrLookup strLookup = new MarkerLookup(); + + @Test + void testLookupEventExistant() { + 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.getName()); + assertEquals(markerName, value); + } + + @Test + void testLookupEventNonExistant() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")) + .build(); + final String value = strLookup.lookup(event, ABSENT_MARKER_NAME); + assertNull(value); + } + + @Test + void testLookupEventNonExistantKey() { + 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, ABSENT_MARKER_NAME); + assertEquals(markerName, value); + } + + @Test + void testLookupEventNullNonExistant() { + final String value = strLookup.lookup(null, ABSENT_MARKER_NAME); + assertNull(value); + } + + @Test + void testLookupExistant() { + final String value = + strLookup.lookup(MarkerManager.getMarker(markerName).getName()); + assertEquals(markerName, value); + } + + @Test + void testLookupNonExistant() { + final String value = strLookup.lookup(ABSENT_MARKER_NAME); + assertNull(value); + } +} 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..c12b30284a6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/PropertiesLookupTest.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.lookup; + +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.core.config.Property; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertiesLookup}. + */ +class PropertiesLookupTest { + + @Test + void testLookupContextProperty() { + final StrLookup propertiesLookup = + new PropertiesLookup(Property.EMPTY_ARRAY, Collections.singletonMap("A", "1")); + assertEquals("1", propertiesLookup.lookup("A")); + final LookupResult lookupResult = propertiesLookup.evaluate("A"); + assertEquals("1", lookupResult.value()); + assertFalse(lookupResult.isLookupEvaluationAllowedInValue()); + } + + @Test + void testLookupConfigProperty() { + final StrLookup propertiesLookup = + new PropertiesLookup(new Property[] {Property.createProperty("A", "1")}, Collections.emptyMap()); + assertEquals("1", propertiesLookup.lookup("A")); + final LookupResult lookupResult = propertiesLookup.evaluate("A"); + assertEquals("1", lookupResult.value()); + assertTrue(lookupResult.isLookupEvaluationAllowedInValue()); + } + + @Test + void testConfigPropertiesPreferredOverContextProperties() { + final StrLookup propertiesLookup = new PropertiesLookup( + new Property[] {Property.createProperty("A", "1")}, Collections.singletonMap("A", "2")); + assertEquals("1", propertiesLookup.lookup("A")); + final LookupResult lookupResult = propertiesLookup.evaluate("A"); + assertEquals("1", lookupResult.value()); + assertTrue(lookupResult.isLookupEvaluationAllowedInValue()); + } + + @Test + void testEvaluateResultsSupportRecursiveEvaluation() { + final PropertiesLookup lookup = new PropertiesLookup(Collections.singletonMap("key", "value")); + assertFalse(lookup.evaluate("key").isLookupEvaluationAllowedInValue()); + } + + @Test + void testEvaluateReturnsNullWhenKeyIsNotFound() { + final PropertiesLookup lookup = new PropertiesLookup(Collections.emptyMap()); + assertNull(lookup.evaluate("key")); + } + + @Test + void testEvaluateReturnsNullWhenKeyIsNull() { + final PropertiesLookup lookup = new PropertiesLookup(Collections.emptyMap()); + assertNull(lookup.evaluate(null)); + } + + @Test + void testContextPropertiesAreMutable() { + final Map contextProperties = new HashMap<>(); + final PropertiesLookup lookup = new PropertiesLookup(Property.EMPTY_ARRAY, contextProperties); + assertNull(lookup.evaluate("key")); + contextProperties.put("key", "value"); + final LookupResult result = lookup.evaluate("key"); + assertEquals("value", result.value()); + assertFalse(result.isLookupEvaluationAllowedInValue()); + } +} 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..b7c959296c8 --- /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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class ResourceBundleLookupTest { + + @Test + 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 + 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 + void testMissingKey() { + final StrLookup lookup = new ResourceBundleLookup(); + assertNull(lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyUnkown")); + } + + @Test + 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..84b3b621e3c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java @@ -0,0 +1,444 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class StrSubstitutorTest { + + private static final String TESTKEY = "TestKey"; + private static final String TESTVAL = "TestValue"; + + @AfterAll + static void after() { + System.clearProperty(TESTKEY); + } + + @BeforeAll + static void before() { + System.setProperty(TESTKEY, TESTVAL); + } + + @Test + 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 + 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 + 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 + 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 + 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 + 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 RecursiveLookup(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 + void testDefault() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(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 + void testDefaultReferencesLookupValue() { + final Map map = new HashMap<>(); + map.put(TESTKEY, "${java:version}"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); + assertEquals("${java:version}", value); + } + + @Test + void testInfiniteSubstitutionOnString() { + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(new HashMap<>())); + final StrSubstitutor subst = new StrSubstitutor(lookup); + final String infiniteSubstitution = "${${::-${::-$${::-j}}}}"; + assertEquals("j}", subst.replace(infiniteSubstitution)); + } + + @Test + void testInfiniteSubstitutionOnStringBuilder() { + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(new HashMap<>())); + final StrSubstitutor subst = new StrSubstitutor(lookup); + final String infiniteSubstitution = "${${::-${::-$${::-j}}}}"; + assertEquals("j}", subst.replace(null, new StringBuilder(infiniteSubstitution))); + } + + @Test + void testLookup() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(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 + void testLookupsNestedWithoutRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${java:version}", subst.replace("${${lower:C}t${lower:X}:first}")); + } + + @Test + void testLookupThrows() { + final StrSubstitutor subst = new StrSubstitutor(new Interpolator(new StrLookup() { + + @Override + public String lookup(final LogEvent event, final String key) { + return lookup(key); + } + + @Override + public String lookup(final String key) { + if ("throw".equals(key)) { + throw new RuntimeException(); + } + return "success"; + } + })); + assertEquals("success ${foo:throw} success", subst.replace("${foo:a} ${foo:throw} ${foo:c}")); + } + + @Test + void testNestedSelfReferenceWithRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("first", "${${ctx:first}}"); + final StrLookup lookup = new Interpolator(new RecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${${ctx:first}}", subst.replace("${ctx:first}")); + } + + @Test + void testNoRecursiveEvaluationWithDefault() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + map.put("second", "${java:runtime}"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${java:version}", subst.replace("${ctx:first:-${ctx:second}}")); + } + + @Test + void testNoRecursiveEvaluationWithDepthOne() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${java:version}", subst.replace("${ctx:first}")); + } + + @Test + void testRandomWithRecursiveDefault() { + final Map map = new HashMap<>(); + map.put("first", "${env:RANDOM:-${ctx:first}}"); + final StrLookup lookup = new Interpolator(new RecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${ctx:first}", subst.replace("${ctx:first}")); + } + + @Test + void testRecursiveSubstitution() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first}"); + map.put("second", "secondValue"); + final StrLookup lookup = new Interpolator(new RecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${ctx:first} and secondValue", subst.replace("${ctx:first} and ${ctx:second}")); + } + + @Test + void testRecursiveWithDefault() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first:-default}"); + final StrLookup lookup = new Interpolator(new RecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("default", subst.replace("${ctx:first}")); + } + + @Test + void testRecursiveWithRecursiveDefault() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first:-${ctx:first}}"); + final StrLookup lookup = new Interpolator(new RecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("${ctx:first}", subst.replace("${ctx:first}")); + } + + @Test + void testReplaceProperties() { + final Properties properties = new Properties(); + properties.put("a", "A"); + assertNull(StrSubstitutor.replace((String) null, properties)); + assertNull(StrSubstitutor.replace((String) null, (Properties) null)); + assertEquals("A", StrSubstitutor.replace("${a}", properties)); + assertEquals("${a}", StrSubstitutor.replace("${a}", (Properties) null)); + } + + @Test + void testTopLevelLookupsWithoutRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("key", "VaLuE"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("value", subst.replace("${lower:${ctx:key}}")); + } + + @Test + void testTopLevelLookupsWithoutRecursiveEvaluation_doubleLower() { + final Map map = new HashMap<>(); + map.put("key", "VaLuE"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("value", subst.replace("${lower:${lower:${ctx:key}}}")); + } + + @Test + void testTopLevelLookupsWithoutRecursiveEvaluationAndDefaultValueLookup() { + final Map map = new HashMap<>(); + map.put("key2", "TWO"); + final StrLookup lookup = new Interpolator(new NonRecursiveLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + assertEquals("two", subst.replace("${lower:${ctx:key1:-${ctx:key2}}}")); + } + + @Test + void testNonRecursiveReferencesRecursive() { + final StrLookup lookup = new StrLookup() { + @Override + public String lookup(final String key) { + return "unexpected"; + } + + @Override + public String lookup(final LogEvent event, final String key) { + return "unexpected"; + } + + @Override + public LookupResult evaluate(final String key) { + return evaluate(null, key); + } + + @Override + public LookupResult evaluate(final LogEvent event, final String key) { + switch (key) { + case "first": + return new RecursiveLookupResult("${second}"); + case "second": + return new NonRecursiveLookupResult("${third}"); + default: + return new RecursiveLookupResult("should not be used: " + key); + } + } + }; + final StrSubstitutor subst = new StrSubstitutor(lookup); + // First (recursive) expands to second, which is not recursive, so the literal '${third}' is used. + assertEquals("${third}", subst.replace("${first}")); + } + + private static final class RecursiveLookup extends AbstractLookup { + + private final Map properties; + + RecursiveLookup(final Map properties) { + this.properties = properties; + } + + @Override + public String lookup(final LogEvent event, final String key) { + final LookupResult result = evaluate(event, key); + return result == null ? null : result.value(); + } + + @Override + public LookupResult evaluate(final LogEvent event, final String key) { + final String result = key == null ? null : properties.get(key); + return result == null ? null : new RecursiveLookupResult(result); + } + } + + private static final class RecursiveLookupResult implements LookupResult { + + private final String value; + + RecursiveLookupResult(final String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + + @Override + public boolean isLookupEvaluationAllowedInValue() { + return true; + } + } + + private static final class NonRecursiveLookup extends AbstractLookup { + + private final Map properties; + + NonRecursiveLookup(final Map properties) { + this.properties = properties; + } + + @Override + public String lookup(final LogEvent event, final String key) { + final LookupResult result = evaluate(event, key); + return result == null ? null : result.value(); + } + + @Override + public LookupResult evaluate(final LogEvent event, final String key) { + final String result = key == null ? null : properties.get(key); + return result == null ? null : new NonRecursiveLookupResult(result); + } + } + + private static final class NonRecursiveLookupResult implements LookupResult { + + private final String value; + + NonRecursiveLookupResult(final String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + + @Override + public boolean isLookupEvaluationAllowedInValue() { + return false; + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..aea694f6927 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.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.lookup; + +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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.Message; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StructuredDataLookupTest { + + private StructuredDataLookup dataLookup; + + @BeforeEach + void setUp() { + dataLookup = new StructuredDataLookup(); + } + + @Test + void testCorrectEvent() { + final Message msg = new StructuredDataMessage("TestId", "This is a test", "Audit"); + final LogEvent event = + Log4jLogEvent.newBuilder().setLevel(Level.DEBUG).setMessage(msg).build(); + + assertEquals("Audit", dataLookup.lookup(event, StructuredDataLookup.TYPE_KEY)); + assertEquals("Audit", dataLookup.lookup(event, toRootUpperCase(StructuredDataLookup.TYPE_KEY))); + assertEquals("TestId", dataLookup.lookup(event, StructuredDataLookup.ID_KEY)); + assertEquals("TestId", dataLookup.lookup(event, toRootUpperCase(StructuredDataLookup.ID_KEY))); + assertNull(dataLookup.lookup(event, "BadKey")); + assertNull(dataLookup.lookup(event, null)); + } + + @Test + void testNullLookup() { + assertNull(dataLookup.lookup(null, null)); + assertNull(dataLookup.lookup(null)); + } + + @Test + void testWrongEvent() { + final LogEvent mockEvent = mock(LogEvent.class); + // ensure message is not a StructuredDataMessage + when(mockEvent.getMessage()).thenReturn(mock(Message.class)); + assertNull(dataLookup.lookup(mockEvent, "ignored")); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..4063f3558ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class SystemPropertiesLookupTest { + + private static final String TESTKEY = "TestKey"; + private static final String TESTVAL = "TestValue"; + + @BeforeAll + static void before() { + System.setProperty(TESTKEY, TESTVAL); + } + + @AfterAll + static void after() { + System.clearProperty(TESTKEY); + } + + @Test + void testLookup() { + final StrLookup lookup = new SystemPropertiesLookup(); + String value = lookup.lookup(TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("BadKey"); + assertNull(value); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..95351959f81 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.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.message; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.management.ThreadInfo; +import org.apache.logging.log4j.message.ThreadDumpMessage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * Tests that ThreadDumpMessage uses ExtendedThreadInformation when available. + */ +class ExtendedThreadInformationTest { + @Test + void testMessage() { + final ThreadDumpMessage msg = new ThreadDumpMessage("Testing"); + + final String message = msg.getFormattedMessage(); + // System.out.print(message); + assertTrue(message.contains(" Id="), "No header"); + } + + @ParameterizedTest + @EnumSource(Thread.State.class) + void testMessageWithNullStackTrace(final Thread.State state) { + obtainMessageWithMissingStackTrace(state, null); + } + + @ParameterizedTest + @EnumSource(Thread.State.class) + void testMessageWithEmptyStackTrace(final Thread.State state) { + obtainMessageWithMissingStackTrace(state, new StackTraceElement[0]); + } + + private void obtainMessageWithMissingStackTrace(final Thread.State state, final StackTraceElement[] stackTrace) { + // setup + final String threadName = "the thread name"; + final long threadId = 23523L; + + final ThreadInfo threadInfo = mock(ThreadInfo.class); + when(threadInfo.getStackTrace()).thenReturn(stackTrace); + when(threadInfo.getThreadName()).thenReturn(threadName); + when(threadInfo.getThreadId()).thenReturn(threadId); + when(threadInfo.isSuspended()).thenReturn(true); + when(threadInfo.isInNative()).thenReturn(true); + when(threadInfo.getThreadState()).thenReturn(state); + + // given + final ExtendedThreadInformation sut = new ExtendedThreadInformation(threadInfo); + + // when + final StringBuilder result = new StringBuilder(); + sut.printThreadInfo(result); + + // then + assertThat(result.toString(), containsString(threadName)); + assertThat(result.toString(), containsString(state.name())); + assertThat(result.toString(), containsString(String.valueOf(threadId))); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/JndiManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/JndiManagerTest.java new file mode 100644 index 00000000000..5a2660610f3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/JndiManagerTest.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.net; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Properties; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link JndiManager}. + */ +class JndiManagerTest { + + private static final String TRUE = "true"; + + @Test + void testIsJndiContextSelectorEnabled() { + assertFalse(JndiManager.isJndiContextSelectorEnabled()); + try { + System.setProperty("log4j2.enableJndiContextSelector", TRUE); + assertTrue(JndiManager.isJndiContextSelectorEnabled()); + } finally { + System.clearProperty("log4j2.enableJndiContextSelector"); + } + } + + @Test + void testIsJndiEnabled() { + assertFalse(JndiManager.isJndiEnabled()); + try { + System.setProperty("log4j2.enableJndiJms", TRUE); + assertTrue(JndiManager.isJndiEnabled()); + } finally { + System.clearProperty("log4j2.enableJndiJms"); + } + } + + @Test + void testIsJndiJdbcEnabled() { + assertFalse(JndiManager.isJndiJdbcEnabled()); + try { + System.setProperty("log4j2.enableJndiJdbc", TRUE); + assertTrue(JndiManager.isJndiJdbcEnabled()); + } finally { + System.clearProperty("log4j2.enableJndiJdbc"); + } + } + + @Test + void testIsJndiJmsEnabled() { + assertFalse(JndiManager.isJndiJmsEnabled()); + try { + System.setProperty("log4j2.enableJndiJms", TRUE); + assertTrue(JndiManager.isJndiJmsEnabled()); + } finally { + System.clearProperty("log4j2.enableJndiJms"); + } + } + + @Test + void testIsJndiLookupEnabled() { + assertFalse(JndiManager.isJndiLookupEnabled()); + } + + @Test + void testNoInstanceByDefault() { + assertThrows(IllegalStateException.class, () -> JndiManager.getDefaultManager()); + assertThrows(IllegalStateException.class, () -> JndiManager.getDefaultManager(null)); + assertThrows(IllegalStateException.class, () -> JndiManager.getDefaultManager("A")); + assertThrows(IllegalStateException.class, () -> JndiManager.getJndiManager(null)); + assertThrows(IllegalStateException.class, () -> JndiManager.getJndiManager(new Properties())); + assertThrows(IllegalStateException.class, () -> JndiManager.getJndiManager(null, null, null, null, null, null)); + assertThrows( + IllegalStateException.class, + () -> JndiManager.getJndiManager("A", "A", "A", "A", "A", new Properties())); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..d325f13fed7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.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.net; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.Level; +import org.junit.jupiter.api.Test; + +class PriorityTest { + + @Test + void testP1() { + final int p = Priority.getPriority(Facility.AUTH, Level.INFO); + 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/SmtpManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.java new file mode 100644 index 00000000000..5e3dd846a1c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SmtpManagerTest.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.net; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.SmtpAppender; +import org.apache.logging.log4j.core.async.RingBufferLogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.core.util.ClockFactory; +import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.message.ReusableSimpleMessage; +import org.junit.jupiter.api.Test; + +class SmtpManagerTest { + + @Test + void testCreateManagerName() { + final String managerName = SmtpManager.createManagerName( + "to", + "cc", + null, + "from", + null, + "LOG4J2-3107", + "proto", + "smtp.log4j.com", + 4711, + "username", + false, + "filter"); + assertEquals("SMTP:to:cc::from::LOG4J2-3107:proto:smtp.log4j.com:4711:username::filter", managerName); + } + + private void testAdd(final LogEvent event) { + final SmtpAppender appender = SmtpAppender.newBuilder() + .setName("smtp") + .setTo("to") + .setCc("cc") + .setBcc("bcc") + .setFrom("from") + .setReplyTo("replyTo") + .setSubject("subject") + .setSmtpProtocol("smtp") + .setSmtpHost("host") + .setSmtpPort(0) + .setSmtpUsername("username") + .setSmtpPassword("password") + .setSmtpDebug(false) + .setFilter(null) + .setBufferSize(10) + .build(); + final MailManager mailManager = appender.getManager(); + assertThat(mailManager).isInstanceOf(SmtpManager.class); + final SmtpManager smtpManager = (SmtpManager) mailManager; + smtpManager.removeAllBufferedEvents(); // in case this smtpManager is reused + smtpManager.add(event); + + final LogEvent[] bufferedEvents = smtpManager.removeAllBufferedEvents(); + assertThat(bufferedEvents).as("Buffered events").hasSize(1); + assertThat(bufferedEvents[0].getMessage()).as("Immutable message").isNotInstanceOf(ReusableMessage.class); + } + + // LOG4J2-3172: make sure existing protections are not violated + @Test + void testAdd_WhereLog4jLogEventWithReusableMessage() { + final LogEvent event = new Log4jLogEvent.Builder() + .setMessage(getReusableMessage("test message")) + .build(); + testAdd(event); + } + + // LOG4J2-3172: make sure existing protections are not violated + @Test + void testAdd_WhereMutableLogEvent() { + final MutableLogEvent event = new MutableLogEvent(new StringBuilder("test message"), null); + testAdd(event); + } + + // LOG4J2-3172 + @Test + void testAdd_WhereRingBufferLogEvent() { + final RingBufferLogEvent event = new RingBufferLogEvent(); + event.setValues( + null, + null, + null, + null, + null, + getReusableMessage("test message"), + null, + null, + null, + 0, + null, + 0, + null, + ClockFactory.getClock(), + new DummyNanoClock()); + testAdd(event); + } + + private ReusableMessage getReusableMessage(final String text) { + final ReusableSimpleMessage message = new ReusableSimpleMessage(); + message.set(text); + return message; + } +} 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..6ba0ee9a918 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static java.util.Objects.requireNonNull; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.apache.logging.log4j.core.config.ConfigurationSourceTest.PATH_IN_JAR; +import static org.apache.logging.log4j.core.net.WireMockUtil.createMapping; +import static org.assertj.core.api.Assertions.assertThat; +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 com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sun.management.UnixOperatingSystemMXBean; +import java.io.ByteArrayOutputStream; +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.nio.file.Path; +import java.nio.file.Paths; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.ConfigurationSourceTest; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; +import org.junitpioneer.jupiter.RetryingTest; + +/** + * Tests the UrlConnectionFactory + */ +@WireMockTest +@SetTestProperty(key = "log4j2.configurationAllowedProtocols", value = "jar,http") +class UrlConnectionFactoryTest { + + private static final Logger LOGGER = LogManager.getLogger(UrlConnectionFactoryTest.class); + + private static final String URL_PATH = "/log4j2-config.xml"; + private static final BasicCredentials CREDENTIALS = new BasicCredentials("testUser", "password"); + private static final byte[] CONFIG_FILE_BODY; + private static final String CONTENT_TYPE = "application/xml"; + + static { + try (InputStream input = requireNonNull( + UrlConnectionFactoryTest.class.getClassLoader().getResourceAsStream("log4j2-config.xml"))) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + IOUtils.copy(input, output); + CONFIG_FILE_BODY = output.toByteArray(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + @AfterEach + void cleanup(WireMockRuntimeInfo info) { + info.getWireMock().removeMappings(); + } + + @Test + @SetTestProperty(key = "log4j2.configurationUsername", value = "foo") + @SetTestProperty(key = "log4j2.configurationPassword", value = "bar") + void testBadCredentials(WireMockRuntimeInfo info) throws Exception { + WireMock wireMock = info.getWireMock(); + // RFC 1123 format rounds to full seconds + ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS); + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, CONFIG_FILE_BODY, CONTENT_TYPE, now)); + final URI uri = new URI(info.getHttpBaseUrl() + URL_PATH); + final ConfigurationSource source = ConfigurationSource.fromUri(uri); + assertNull(source, "A ConfigurationSource should not have been returned"); + } + + @Test + @SetTestProperty(key = "log4j2.configurationUsername", value = "testUser") + @SetTestProperty(key = "log4j2.configurationPassword", value = "password") + void withAuthentication(WireMockRuntimeInfo info) throws Exception { + WireMock wireMock = info.getWireMock(); + // RFC 1123 format rounds to full seconds + ZonedDateTime now = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS); + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, CONFIG_FILE_BODY, CONTENT_TYPE, now)); + final URI uri = new URI(info.getHttpBaseUrl() + URL_PATH); + final ConfigurationSource source = ConfigurationSource.fromUri(uri); + assertNotNull(source, "No ConfigurationSource returned"); + final InputStream is = source.getInputStream(); + assertNotNull(is, "No data returned"); + is.close(); + final long lastModified = source.getLastModified(); + assertThat(lastModified).isEqualTo(now.toInstant().toEpochMilli()); + int result = verifyNotModified(uri, lastModified); + assertEquals(SC_NOT_MODIFIED, result, "File was modified"); + + wireMock.removeMappings(); + now = now.plusMinutes(5); + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, CONFIG_FILE_BODY, CONTENT_TYPE, now)); + result = verifyNotModified(uri, lastModified); + assertEquals(SC_OK, result, "File was not modified"); + } + + private int verifyNotModified(final URI uri, final long lastModifiedMillis) throws Exception { + AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + HttpURLConnection urlConnection = + UrlConnectionFactory.createConnection(uri.toURL(), lastModifiedMillis, null, provider); + urlConnection.connect(); + + try { + return urlConnection.getResponseCode(); + } catch (final IOException ioe) { + LOGGER.error("Error accessing configuration at {}: {}", uri, ioe.getMessage()); + return SC_INTERNAL_SERVER_ERROR; + } + } + + @RetryingTest(maxAttempts = 5, suspendForMs = 1000) + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "Fails frequently on Windows (#2011)") + @SetTestProperty(key = "log4j2.configurationUsername", value = "testUser") + @SetTestProperty(key = "log4j2.configurationPassword", value = "password") + void testNoJarFileLeak(@TempDir Path dir, WireMockRuntimeInfo info) throws Exception { + Path jarFile = ConfigurationSourceTest.prepareJarConfigURL(dir); + // Retrieve using 'file:' + URL jarUrl = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); + long expected = getOpenFileDescriptorCount(); + UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); + assertEquals(expected, getOpenFileDescriptorCount()); + + // Prepare mock + ByteArrayOutputStream body = new ByteArrayOutputStream(); + try (InputStream inputStream = Files.newInputStream(jarFile)) { + IOUtils.copy(inputStream, body); + } + WireMock wireMock = info.getWireMock(); + wireMock.register(WireMock.get("/jarFile.jar") + .willReturn( + aResponse().withStatus(200).withBodyFile("jarFile.jar").withBody(body.toByteArray()))); + // Retrieve using 'http:' + jarUrl = new URL("jar:" + info.getHttpBaseUrl() + "/jarFile.jar!" + PATH_IN_JAR); + // URLConnection leaves JAR files in the temporary directory + Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); + List expectedFiles; + try (Stream stream = Files.list(tmpDir)) { + expectedFiles = stream.collect(Collectors.toList()); + } + UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); + List actualFiles; + try (Stream stream = Files.list(tmpDir)) { + actualFiles = stream.collect(Collectors.toList()); + } + assertThat(actualFiles).containsExactlyElementsOf(expectedFiles); + } + + 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/net/WireMockUtil.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java new file mode 100644 index 00000000000..f8655742320 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.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.net; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.absent; +import static com.github.tomakehurst.wiremock.client.WireMock.after; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.before; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToDateTime; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.notContaining; +import static com.github.tomakehurst.wiremock.stubbing.StubImport.stubImport; +import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.net.HttpHeaders.IF_MODIFIED_SINCE; +import static com.google.common.net.HttpHeaders.LAST_MODIFIED; + +import com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.stubbing.StubImport; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +public class WireMockUtil { + + private static final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); + + /** + * Establishes a set of mapping to serve a file + * + * @param urlPath The URL path of the served file + * @param credentials The credentials to use for authentication + * @param body The body of the file + * @param contentType The MIME content type of the file + * @param lastModified The last modification date of the file + * @return A set of mappings + */ + public static StubImport createMapping( + String urlPath, BasicCredentials credentials, byte[] body, String contentType, ZonedDateTime lastModified) { + int idx = urlPath.lastIndexOf('/'); + String fileName = idx == -1 ? urlPath : urlPath.substring(idx + 1); + return stubImport() + // Lack of authentication data + .stub(get(anyUrl()) + .withHeader(AUTHORIZATION, absent()) + .willReturn(aResponse().withStatus(401).withStatusMessage("Not Authenticated"))) + // Wrong authentication data + .stub(get(anyUrl()) + .withHeader(AUTHORIZATION, notContaining(credentials.asAuthorizationHeaderValue())) + .willReturn(aResponse().withStatus(403).withStatusMessage("Not Authorized"))) + // Serves the file + .stub(get(urlPath) + .withBasicAuth(credentials.username, credentials.password) + .withHeader(IF_MODIFIED_SINCE, before(lastModified).or(absent())) + .willReturn(aResponse() + .withStatus(200) + .withBodyFile(fileName) + .withBody(body) + .withHeader(LAST_MODIFIED, formatter.format(lastModified)) + .withHeader(CONTENT_TYPE, contentType))) + // The file was not updated since lastModified + .stub(get(urlPath) + .withBasicAuth(credentials.username, credentials.password) + .withHeader(IF_MODIFIED_SINCE, after(lastModified).or(equalToDateTime(lastModified))) + .willReturn( + aResponse().withStatus(304).withHeader(LAST_MODIFIED, formatter.format(lastModified)))) + .build(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..1f7500345d2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class EnvironmentPasswordProviderTest { + + @Test + void testConstructorDisallowsNull() { + assertThrows(NullPointerException.class, () -> new EnvironmentPasswordProvider(null)); + } + + @Test + void testGetPasswordReturnsEnvironmentVariableValue() { + final String value = System.getenv("PATH"); + if (value == null) { + return; // we cannot test in this environment + } + final char[] actual = new EnvironmentPasswordProvider("PATH").getPassword(); + assertArrayEquals(value.toCharArray(), actual); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..c7d2571f667 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class FilePasswordProviderTest { + + @Test + void testGetPassword() throws Exception { + final String PASSWORD = "myPass123"; + final Path path = Files.createTempFile("testPass", ".txt"); + Files.write(path, PASSWORD.getBytes(Charset.defaultCharset())); + + final char[] actual = new FilePasswordProvider(path.toString()).getPassword(); + Files.delete(path); + assertArrayEquals(PASSWORD.toCharArray(), actual); + } + + @Test + void testConstructorDisallowsNull() { + assertThrows(NullPointerException.class, () -> new FilePasswordProvider(null)); + } + + @Test + void testConstructorFailsIfFileDoesNotExist() { + 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..1889d5b2aa0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.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.core.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.KeyStore; +import java.util.Collections; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +@UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure +@SetSystemProperty(key = "sun.security.mscapi.keyStoreCompatibilityMode", value = "false") +class KeyStoreConfigurationTest { + + @SuppressWarnings("deprecation") + @Test + void loadEmptyConfigurationDeprecated() { + assertThrows( + StoreConfigurationException.class, + () -> new KeyStoreConfiguration(null, SslKeyStoreConstants.NULL_PWD, null, null)); + } + + @Test + void loadEmptyConfiguration() { + assertThrows( + StoreConfigurationException.class, + () -> new KeyStoreConfiguration( + null, new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), null, null)); + } + + @Test + void loadNotEmptyConfigurationDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + SslKeyStoreConstants.KEYSTORE_PWD(), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + checkKeystoreConfiguration(ksc); + } + + static Stream configurations() { + final Stream.Builder builder = Stream.builder(); + builder.add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_PWD, + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_TYPE)); + if (OS.WINDOWS.isCurrentOs()) { + builder.add(Arguments.of(null, (Supplier) () -> null, SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE)) + .add(Arguments.of( + null, (Supplier) () -> null, SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE)); + } + return builder.build(); + } + + @ParameterizedTest + @MethodSource("configurations") + void loadNotEmptyConfiguration( + final String keystoreFile, final Supplier password, final String keystoreType) + throws StoreConfigurationException { + final KeyStoreConfiguration ksc = + new KeyStoreConfiguration(keystoreFile, new MemoryPasswordProvider(password.get()), keystoreType, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + checkKeystoreConfiguration(ksc); + } + + @Test + void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + SslKeyStoreConstants.KEYSTORE_PWD(), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @Test + void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @SuppressWarnings("deprecation") + @Test + void wrongPasswordDeprecated() { + assertThrows( + StoreConfigurationException.class, + () -> new KeyStoreConfiguration(SslKeyStoreConstants.KEYSTORE_LOCATION, "wrongPassword!", null, null)); + } + + static Stream wrongConfigurations() { + final Stream.Builder builder = Stream.builder(); + builder.add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_LOCATION, + (Supplier) () -> "wrongPassword!".toCharArray(), + SslKeyStoreConstants.KEYSTORE_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)) + .add(Arguments.of( + SslKeyStoreConstants.KEYSTORE_P12_LOCATION, + (Supplier) SslKeyStoreConstants::KEYSTORE_P12_NOPASS_PWD, + SslKeyStoreConstants.KEYSTORE_P12_TYPE)); + if (OS.WINDOWS.isCurrentOs()) { + builder.add(Arguments.of( + null, (Supplier) () -> new char[0], SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE)) + .add(Arguments.of( + null, (Supplier) () -> new char[0], SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE)); + } + return builder.build(); + } + + @ParameterizedTest + @MethodSource("wrongConfigurations") + void wrongPassword(final String keystoreFile, final Supplier password, final String keystoreType) { + assertThrows( + StoreConfigurationException.class, + () -> new KeyStoreConfiguration( + keystoreFile, new MemoryPasswordProvider(password.get()), keystoreType, null)); + } + + static void checkKeystoreConfiguration(final AbstractKeyStoreConfiguration config) { + // Not all keystores throw immediately if the password is wrong + assertDoesNotThrow(() -> { + final KeyStore ks = config.load(); + for (final String alias : Collections.list(ks.aliases())) { + if (ks.isCertificateEntry(alias)) { + ks.getCertificate(alias); + } + if (ks.isKeyEntry(alias)) { + ks.getKey(alias, config.getPasswordAsCharArray()); + } + } + }); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..068ddb275db --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class MemoryPasswordProviderTest { + @Test + void testConstructorAllowsNull() { + assertNull(new MemoryPasswordProvider(null).getPassword()); + } + + @Test + void testConstructorDoesNotModifyOriginalParameterArray() { + final char[] initial = "123".toCharArray(); + new MemoryPasswordProvider(initial); + assertArrayEquals("123".toCharArray(), initial); + } + + @Test + void testGetPasswordReturnsCopyOfConstructorArray() { + final char[] initial = "123".toCharArray(); + final MemoryPasswordProvider provider = new MemoryPasswordProvider(initial); + final char[] actual = provider.getPassword(); + assertArrayEquals("123".toCharArray(), actual); + assertNotSame(initial, actual); + + Arrays.fill(initial, 'a'); + assertArrayEquals("123".toCharArray(), provider.getPassword()); + assertNotSame(provider.getPassword(), provider.getPassword()); + } +} 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..308f7546ef0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Properties; +import java.util.stream.Stream; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +// Suppresses `StatusLogger` output, unless there is a failure +@UsingStatusListener +class SslConfigurationFactoryTest { + + private static final String TRUSTSTORE_LOCATION_PROP_NAME = "log4j2.trustStoreLocation"; + + private static final String TRUSTSTORE_PASSWORD_PROP_NAME = "log4j2.trustStorePassword"; + + private static final String TRUSTSTORE_TYPE_PROP_NAME = "log4j2.trustStoreKeyStoreType"; + + private static final String KEYSTORE_LOCATION_PROP_NAME = "log4j2.keyStoreLocation"; + + private static final String KEYSTORE_PASSWORD_PROP_NAME = "log4j2.keyStorePassword"; + + private static final String KEYSTORE_TYPE_PROP_NAME = "log4j2.keyStoreType"; + + private static void addKeystoreConfiguration(final Properties props) { + props.setProperty(KEYSTORE_LOCATION_PROP_NAME, SslKeyStoreConstants.KEYSTORE_LOCATION); + props.setProperty(KEYSTORE_TYPE_PROP_NAME, SslKeyStoreConstants.KEYSTORE_TYPE); + } + + private static void addTruststoreConfiguration(final Properties props) { + props.setProperty(TRUSTSTORE_LOCATION_PROP_NAME, SslKeyStoreConstants.TRUSTSTORE_LOCATION); + props.setProperty(TRUSTSTORE_TYPE_PROP_NAME, SslKeyStoreConstants.TRUSTSTORE_TYPE); + } + + @Test + void testStaticConfiguration() { + + // Case 1: Empty configuration + final Properties props = new Properties(); + final PropertiesUtil util = new PropertiesUtil(props); + SslConfiguration sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNull(sslConfiguration); + + // Case 2: Only key store + props.clear(); + addKeystoreConfiguration(props); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNotNull(sslConfiguration.getKeyStoreConfig()); + assertNull(sslConfiguration.getTrustStoreConfig()); + + // Case 3: Only trust store + props.clear(); + addTruststoreConfiguration(props); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNull(sslConfiguration.getKeyStoreConfig()); + assertNotNull(sslConfiguration.getTrustStoreConfig()); + + // Case 4: Both key and trust stores + props.clear(); + addKeystoreConfiguration(props); + addTruststoreConfiguration(props); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNotNull(sslConfiguration.getKeyStoreConfig()); + assertNotNull(sslConfiguration.getTrustStoreConfig()); + } + + static Stream windowsKeystoreConfigs() { + final String[] emptyOrNull = {"", null}; + final Stream.Builder builder = Stream.builder(); + for (final String location : emptyOrNull) { + for (final String password : emptyOrNull) { + builder.add(Arguments.of(location, password)); + } + } + return builder.build(); + } + + @EnabledOnOs(OS.WINDOWS) + @ParameterizedTest + @MethodSource("windowsKeystoreConfigs") + void testPasswordLessStores(final String location, final String password) { + + // Create the configuration + final Properties props = new Properties(); + props.setProperty(KEYSTORE_TYPE_PROP_NAME, SslKeyStoreConstants.WINDOWS_KEYSTORE_TYPE); + props.setProperty(TRUSTSTORE_TYPE_PROP_NAME, SslKeyStoreConstants.WINDOWS_TRUSTSTORE_TYPE); + if (location != null) { + props.setProperty(KEYSTORE_LOCATION_PROP_NAME, location); + props.setProperty(TRUSTSTORE_LOCATION_PROP_NAME, location); + } + if (password != null) { + props.setProperty(KEYSTORE_PASSWORD_PROP_NAME, password); + props.setProperty(TRUSTSTORE_PASSWORD_PROP_NAME, password); + } + final PropertiesUtil util = new PropertiesUtil(props); + final SslConfiguration config = SslConfigurationFactory.createSslConfiguration(util); + + // Verify the configuration + assertNotNull(config); + final KeyStoreConfiguration keyStoreConfig = config.getKeyStoreConfig(); + assertNotNull(keyStoreConfig); + KeyStoreConfigurationTest.checkKeystoreConfiguration(keyStoreConfig); + final TrustStoreConfiguration trustStoreConfig = config.getTrustStoreConfig(); + assertNotNull(trustStoreConfig); + KeyStoreConfigurationTest.checkKeystoreConfiguration(trustStoreConfig); + } +} 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..93d02712d5e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.UnknownHostException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +class SslConfigurationTest { + + private static final String TLS_TEST_HOST = "apache.org"; + private static final int TLS_TEST_PORT = 443; + + private static SslConfiguration createTestSslConfigurationResources() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + null, + null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + private static SslConfiguration createTestSslConfigurationFiles() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.KEYSTORE_PWD()), + SslKeyStoreConstants.KEYSTORE_TYPE, + null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + SslKeyStoreConstants.TRUSTSTORE_TYPE, + null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + @Test + void testGettersFromScratchFiles() throws StoreConfigurationException { + assertNotNull(createTestSslConfigurationFiles().getProtocol()); + assertNotNull(createTestSslConfigurationFiles().getKeyStoreConfig()); + assertNotNull(createTestSslConfigurationFiles().getSslContext()); + assertNotNull(createTestSslConfigurationFiles().getSslContext().getSocketFactory()); + assertNotNull(createTestSslConfigurationFiles().getTrustStoreConfig()); + } + + @Test + void testGettersFromScratchResources() throws StoreConfigurationException { + assertNotNull(createTestSslConfigurationResources().getProtocol()); + assertNotNull(createTestSslConfigurationResources().getKeyStoreConfig()); + assertNotNull(createTestSslConfigurationResources().getSslContext()); + assertNotNull(createTestSslConfigurationResources().getSslContext().getSocketFactory()); + assertNotNull(createTestSslConfigurationResources().getTrustStoreConfig()); + } + + @Test + void testEquals() { + assertEquals( + SslConfiguration.createSSLConfiguration(null, null, null), + SslConfiguration.createSSLConfiguration(null, null, null)); + } + + @Test + void emptyConfigurationDoesNotCauseNullSSLSocketFactory() { + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); + assertNotNull(factory); + } + + @Test + void emptyConfigurationHasDefaultTrustStore() throws IOException { + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); + try { + try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { + assertNotNull(clientSocket); + } + } catch (final UnknownHostException offline) { + // this exception is thrown on Windows when offline + } + } + + @Test + void connectionFailsWithoutValidServerCertificate() throws IOException, StoreConfigurationException { + final TrustStoreConfiguration tsc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), + null, + null); + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, tsc); + final SSLSocketFactory factory = sc.getSslContext().getSocketFactory(); + 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 offline) { + // this exception is thrown on Windows when offline + } + } + + @Test + @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure + void loadKeyStoreWithoutPassword() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration( + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), + SslKeyStoreConstants.KEYSTORE_P12_NOPASS_TYPE, + null); + final SslConfiguration sslConf = SslConfiguration.createSSLConfiguration(null, ksc, null); + final SSLSocketFactory factory = sslConf.getSslContext().getSocketFactory(); + assertNotNull(factory); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.java new file mode 100644 index 00000000000..387b0f46bd3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslKeyStoreConstants.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; + +public final class SslKeyStoreConstants { + + private static final String PATH = "src/test/resources/org/apache/logging/log4j/core/net/ssl/"; + + /// Trust store (JKS) ///////////////////////////////////////////////////// + + public static final String TRUSTSTORE_LOCATION = PATH + "trustStore.jks"; + + public static char[] TRUSTSTORE_PWD() { + return "aTrustStoreSecret".toCharArray(); + } + + public static final String TRUSTSTORE_TYPE = "JKS"; + + /// Trust store #2 (JKS) ////////////////////////////////////////////////// + + public static final String TRUSTSTORE2_LOCATION = PATH + "trustStore2.jks"; + + public static char[] TRUSTSTORE2_PWD() { + return "aTrustStoreSecret2".toCharArray(); + } + + public static final String TRUSTSTORE2_TYPE = "JKS"; + + /// Key store (JKS) /////////////////////////////////////////////////////// + + public static final String KEYSTORE_LOCATION = PATH + "keyStore.jks"; + + public static char[] KEYSTORE_PWD() { + return "aKeyStoreSecret".toCharArray(); + } + + public static final String KEYSTORE_TYPE = "JKS"; + + /// Key store #2 (JKS) //////////////////////////////////////////////////// + + public static final String KEYSTORE2_LOCATION = PATH + "keyStore2.jks"; + + public static char[] KEYSTORE2_PWD() { + return "aKeyStoreSecret2".toCharArray(); + } + + public static final String KEYSTORE2_TYPE = "JKS"; + + /// Key store (P12) /////////////////////////////////////////////////////// + + public static final String KEYSTORE_P12_LOCATION = PATH + "keyStore.p12"; + + public static char[] KEYSTORE_P12_PWD() { + return "aKeyStoreSecret".toCharArray(); + } + + public static final String KEYSTORE_P12_TYPE = "PKCS12"; + + /// Key store (P12 without password) ////////////////////////////////////// + + public static final String KEYSTORE_P12_NOPASS_LOCATION = PATH + "keyStore-nopass.p12"; + + public static char[] KEYSTORE_P12_NOPASS_PWD() { + return new char[0]; + } + + public static final String KEYSTORE_P12_NOPASS_TYPE = "PKCS12"; + + /// Other ///////////////////////////////////////////////////////////////// + + public static final char[] NULL_PWD = null; + + public static final String WINDOWS_KEYSTORE_TYPE = "Windows-MY"; + + public static final String WINDOWS_TRUSTSTORE_TYPE = "Windows-ROOT"; +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..39aee7bf08d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled +class StoreConfigurationTest> { + + @Test + void equalsWithNotNullValues() { + final String location = "/to/the/file.jks"; + final PasswordProvider password = new MemoryPasswordProvider("changeit".toCharArray()); + final StoreConfiguration a = new StoreConfiguration<>(location, password); + final StoreConfiguration b = new StoreConfiguration<>(location, password); + + assertEquals(b, a); + assertEquals(a, b); + } + + @Test + void notEqualsWithNullAndNotNullValues() { + final String location = "/to/the/file.jks"; + final PasswordProvider password = new MemoryPasswordProvider("changeit".toCharArray()); + final StoreConfiguration a = new StoreConfiguration<>(location, password); + final StoreConfiguration b = new StoreConfiguration<>(null, new MemoryPasswordProvider(null)); + + assertNotEquals(a, b); + assertNotEquals(b, a); + } + + @Test + void equalsWithNullValues() { + final StoreConfiguration a = new StoreConfiguration<>(null, new MemoryPasswordProvider(null)); + final StoreConfiguration b = new StoreConfiguration<>(null, new MemoryPasswordProvider(null)); + + assertEquals(b, a); + assertEquals(a, b); + } +} 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..72b605e22b4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.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.net.ssl; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.KeyStore; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +// Suppresses `StatusLogger` output, unless there is a failure +@UsingStatusListener +class TrustStoreConfigurationTest { + @SuppressWarnings("deprecation") + @Test + void loadEmptyConfigurationDeprecated() { + assertThrows( + StoreConfigurationException.class, + () -> new TrustStoreConfiguration(null, SslKeyStoreConstants.NULL_PWD, null, null)); + } + + @Test + void loadEmptyConfiguration() { + assertThrows( + StoreConfigurationException.class, + () -> new TrustStoreConfiguration( + null, new MemoryPasswordProvider(SslKeyStoreConstants.NULL_PWD), null, null)); + } + + @Test + void loadConfigurationDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants.TRUSTSTORE_PWD(), null, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + void loadConfiguration() throws StoreConfigurationException { + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + null, + null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, SslKeyStoreConstants.TRUSTSTORE_PWD(), null, null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @Test + void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { + final TrustStoreConfiguration ksc = new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider(SslKeyStoreConstants.TRUSTSTORE_PWD()), + null, + null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @SuppressWarnings("deprecation") + @Test + void wrongPasswordDeprecated() { + assertThrows( + StoreConfigurationException.class, + () -> new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, "wrongPassword!".toCharArray(), null, null)); + } + + @Test + void wrongPassword() { + assertThrows( + StoreConfigurationException.class, + () -> new TrustStoreConfiguration( + SslKeyStoreConstants.TRUSTSTORE_LOCATION, + new MemoryPasswordProvider("wrongPassword!".toCharArray()), + null, + null)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/JsonLogEventParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/JsonLogEventParserTest.java new file mode 100644 index 00000000000..8cc81481f4e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/JsonLogEventParserTest.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class JsonLogEventParserTest extends LogEventParserTest { + + private JsonLogEventParser parser; + + private static final String JSON = "{\n" + " \"timeMillis\" : 1493121664118,\n" + + " \"instant\":{\"epochSecond\":1493121664,\"nanoOfSecond\":118000000},\n" + + " \"thread\" : \"main\",\n" + + " \"threadId\" : 1,\n" + + " \"threadPriority\" : 5,\n" + + " \"level\" : \"INFO\",\n" + + " \"loggerName\" : \"HelloWorld\",\n" + + " \"marker\" : {\n" + + " \"name\" : \"child\",\n" + + " \"parents\" : [ {\n" + + " \"name\" : \"parent\",\n" + + " \"parents\" : [ {\n" + + " \"name\" : \"grandparent\"\n" + + " } ]\n" + + " } ]\n" + + " },\n" + + " \"message\" : \"Hello, world!\",\n" + + " \"thrown\" : {\n" + + " \"commonElementCount\" : 0,\n" + + " \"message\" : \"error message\",\n" + + " \"name\" : \"java.lang.RuntimeException\",\n" + + " \"extendedStackTrace\" : [ {\n" + + " \"class\" : \"logtest.Main\",\n" + + " \"method\" : \"main\",\n" + + " \"file\" : \"Main.java\",\n" + + " \"line\" : 29,\n" + + " \"exact\" : true,\n" + + " \"location\" : \"classes/\",\n" + + " \"version\" : \"?\"\n" + + " } ]\n" + + " },\n" + + " \"contextStack\" : [ \"one\", \"two\" ],\n" + + " \"loggerFqcn\" : \"org.apache.logging.log4j.spi.AbstractLogger\",\n" + + " \"endOfBatch\" : false,\n" + + " \"contextMap\" : {\n" + + " \"bar\" : \"BAR\",\n" + + " \"foo\" : \"FOO\"\n" + + " },\n" + + " \"source\" : {\n" + + " \"class\" : \"logtest.Main\",\n" + + " \"method\" : \"main\",\n" + + " \"file\" : \"Main.java\",\n" + + " \"line\" : 29\n" + + " }\n" + + "}"; + + @BeforeEach + void setup() { + parser = new JsonLogEventParser(); + } + + @Test + void testString() throws ParseException { + final LogEvent logEvent = parser.parseFrom(JSON); + assertLogEvent(logEvent); + } + + @Test + void testStringEmpty() { + assertThrows(ParseException.class, () -> parser.parseFrom("")); + } + + @Test + void testStringInvalidJson() { + assertThrows(ParseException.class, () -> parser.parseFrom("foobar")); + } + + @Test + void testStringJsonArray() { + assertThrows(ParseException.class, () -> parser.parseFrom("[]")); + } + + @Test + void testEmptyObject() throws ParseException { + parser.parseFrom("{}"); + } + + @Test + void testStringWrongPropertyType() { + assertThrows(ParseException.class, () -> parser.parseFrom("{\"threadId\":\"foobar\"}")); + } + + @Test + void testStringIgnoreInvalidProperty() throws ParseException { + parser.parseFrom("{\"foo\":\"bar\"}"); + } + + @Test + void testByteArray() throws ParseException { + final LogEvent logEvent = parser.parseFrom(JSON.getBytes(StandardCharsets.UTF_8)); + assertLogEvent(logEvent); + } + + @Test + void testByteArrayOffsetLength() throws ParseException { + final byte[] bytes = ("abc" + JSON + "def").getBytes(StandardCharsets.UTF_8); + final LogEvent logEvent = parser.parseFrom(bytes, 3, bytes.length - 6); + assertLogEvent(logEvent); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java index cdb3bef3359..f8f254f1d61 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java @@ -1,31 +1,30 @@ /* * 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.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.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; public abstract class LogEventParserTest { protected void assertLogEvent(final LogEvent logEvent) { @@ -38,18 +37,16 @@ protected void assertLogEvent(final LogEvent logEvent) { 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.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.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.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/test/java/org/apache/logging/log4j/core/parser/XmlLogEventParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/XmlLogEventParserTest.java new file mode 100644 index 00000000000..0805ffa7684 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/XmlLogEventParserTest.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.parser; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class XmlLogEventParserTest extends LogEventParserTest { + + private XmlLogEventParser parser; + + private static final String XML = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " Hello, world!\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " one\n" + + " two\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + @BeforeEach + void setup() { + parser = new XmlLogEventParser(); + } + + @Test + void testString() throws ParseException { + final LogEvent logEvent = parser.parseFrom(XML); + assertLogEvent(logEvent); + } + + @Test + void testStringEmpty() { + assertThrows(ParseException.class, () -> parser.parseFrom("")); + } + + @Test + void testStringInvalidXml() { + assertThrows(ParseException.class, () -> parser.parseFrom("foobar")); + } + + @Test + void testEmptyObject() throws ParseException { + parser.parseFrom(""); + } + + @Test + void testStringWrongPropertyType() { + assertThrows( + ParseException.class, + () -> parser.parseFrom("foobar")); + } + + @Test + void testTimeMillisIgnored() throws ParseException { + parser.parseFrom("foobar"); + } + + @Test + void testStringIgnoreInvalidProperty() throws ParseException { + parser.parseFrom("bar"); + } + + @Test + void testByteArray() throws ParseException { + final LogEvent logEvent = parser.parseFrom(XML.getBytes(StandardCharsets.UTF_8)); + assertLogEvent(logEvent); + } + + @Test + void testByteArrayOffsetLength() throws ParseException { + final byte[] bytes = ("abc" + XML + "def").getBytes(StandardCharsets.UTF_8); + final LogEvent logEvent = parser.parseFrom(bytes, 3, bytes.length - 6); + assertLogEvent(logEvent); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/YamlLogEventParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/YamlLogEventParserTest.java new file mode 100644 index 00000000000..9e719d1ce7c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/parser/YamlLogEventParserTest.java @@ -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. + */ +package org.apache.logging.log4j.core.parser; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.charset.StandardCharsets; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class YamlLogEventParserTest extends LogEventParserTest { + + private YamlLogEventParser parser; + + private static final String YAML = "---\n" + "timeMillis: 1493121664118\n" + + "instant:\n" + + " epochSecond: 1493121664\n" + + " nanoOfSecond: 118000000\n" + + "thread: \"main\"\n" + + "level: \"INFO\"\n" + + "loggerName: \"HelloWorld\"\n" + + "marker:\n" + + " name: \"child\"\n" + + " parents:\n" + + " - name: \"parent\"\n" + + " parents:\n" + + " - name: \"grandparent\"\n" + + "message: \"Hello, world!\"\n" + + "thrown:\n" + + " commonElementCount: 0\n" + + " message: \"error message\"\n" + + " name: \"java.lang.RuntimeException\"\n" + + " extendedStackTrace:\n" + + " - class: \"logtest.Main\"\n" + + " method: \"main\"\n" + + " file: \"Main.java\"\n" + + " line: 29\n" + + " exact: true\n" + + " location: \"classes/\"\n" + + " version: \"?\"\n" + + "contextStack:\n" + + "- \"one\"\n" + + "- \"two\"\n" + + "endOfBatch: false\n" + + "loggerFqcn: \"org.apache.logging.log4j.spi.AbstractLogger\"\n" + + "contextMap:\n" + + " bar: \"BAR\"\n" + + " foo: \"FOO\"\n" + + "threadId: 1\n" + + "threadPriority: 5\n" + + "source:\n" + + " class: \"logtest.Main\"\n" + + " method: \"main\"\n" + + " file: \"Main.java\"\n" + + " line: 29"; + + @BeforeEach + void setup() { + parser = new YamlLogEventParser(); + } + + @Test + void testString() throws ParseException { + final LogEvent logEvent = parser.parseFrom(YAML); + assertLogEvent(logEvent); + } + + @Test + void testStringEmpty() { + assertThrows(ParseException.class, () -> parser.parseFrom("")); + } + + @Test + void testStringInvalidYaml() { + assertThrows(ParseException.class, () -> parser.parseFrom("foobar")); + } + + @Test + void testEmptyObject() throws ParseException { + parser.parseFrom("---\n"); + } + + @Test + void testTimeMillisIgnored() throws ParseException { + parser.parseFrom("---\ntimeMillis: \"foobar\"\n"); + } + + @Test + void testStringWrongPropertyType() { + assertThrows(ParseException.class, () -> parser.parseFrom("---\nthreadId: \"foobar\"\n")); + } + + @Test + void testStringIgnoreInvalidProperty() throws ParseException { + parser.parseFrom("---\nfoo: \"bar\"\n"); + } + + @Test + void testByteArray() throws ParseException { + final LogEvent logEvent = parser.parseFrom(YAML.getBytes(StandardCharsets.UTF_8)); + assertLogEvent(logEvent); + } + + @Test + void testByteArrayOffsetLength() throws ParseException { + final byte[] bytes = ("abc" + YAML + "def").getBytes(StandardCharsets.UTF_8); + final LogEvent logEvent = parser.parseFrom(bytes, 3, bytes.length - 6); + assertLogEvent(logEvent); + } +} 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..635e4de01c1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +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.Test; + +@LoggerContextSource("log4j2-calling-class.xml") +class CallerInformationTest { + + @Test + 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 + 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/DatePatternConverterTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java new file mode 100644 index 00000000000..16fd89ac30b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java @@ -0,0 +1,355 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; +import java.util.stream.Stream; +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.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +abstract class DatePatternConverterTestBase { + + private static final class MyLogEvent extends AbstractLogEvent { + private static final long serialVersionUID = 0; + + @Override + public Instant getInstant() { + final 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(); + } + } + + private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS"; + + private static final String ISO8601 = "ISO8601"; + + private static final String ISO8601_OFFSET_DATE_TIME_HHMM = "ISO8601_OFFSET_DATE_TIME_HHMM"; + + private static final String ISO8601_OFFSET_DATE_TIME_HHCMM = "ISO8601_OFFSET_DATE_TIME_HHCMM"; + + private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601}; + + private final boolean threadLocalsEnabled; + + DatePatternConverterTestBase(final boolean threadLocalsEnabled) { + this.threadLocalsEnabled = threadLocalsEnabled; + } + + 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(); + } + + @Test + void testThreadLocalsConstant() { + assertEquals(Constants.ENABLE_THREADLOCALS, threadLocalsEnabled); + } + + @Test + void testFormatDateStringBuilderDefaultPattern() { + assertDatePattern(null, date(2001, 1, 1), "2001-02-01 14:15:16,123"); + } + + @SuppressWarnings("deprecation") + @Test + 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 + void testFormatDateStringBuilderIso8601BasicWithPeriod() { + assertDatePattern("ISO8601_BASIC_PERIOD", date(2001, 1, 1), "20010201T141516.123"); + } + + @Test + void testFormatDateStringBuilderIso8601WithPeriod() { + assertDatePattern("ISO8601_PERIOD", date(2001, 1, 1), "2001-02-01T14:15:16.123"); + } + + @SuppressWarnings("deprecation") + @Test + void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() { + final String[] pattern = {"ISO8601_PERIOD_MICROS", "Z"}; + final DatePatternConverter converter = DatePatternConverter.newInstance(pattern); + final StringBuilder sb = new StringBuilder(); + final 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 + void testFormatDateStringBuilderOriginalPattern() { + assertDatePattern("yyyy/MM/dd HH-mm-ss.SSS", date(2001, 1, 1), "2001/02/01 14-15-16.123"); + } + + @Test + 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 + 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 + void testFormatAmericanPatterns() { + final 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.SSSSSS", 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.SSSSSS", date, "11/03/11 14:15:16.123000"); + } + + @SuppressWarnings("deprecation") + private static void assertDatePattern(final String format, final Date date, final String expected) { + final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {format}); + final StringBuilder sb = new StringBuilder(); + converter.format(date, sb); + + assertEquals(expected, sb.toString()); + } + + @Test + 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 + 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 String expected = DateTimeFormatter.ofPattern(converter.getPattern()) + .withZone(ZoneId.systemDefault()) + .format((TemporalAccessor) event.getInstant()); + assertEquals(expected, sb.toString()); + } + + @Test + void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() { + final LogEvent event = new MyLogEvent(); + final String[] optionsWithTimezone = {ISO8601_OFFSET_DATE_TIME_HHMM}; + final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final String expected = DateTimeFormatter.ofPattern(converter.getPattern()) + .withZone(ZoneId.systemDefault()) + .format((TemporalAccessor) event.getInstant()); + assertEquals(expected, sb.toString()); + } + + @Test + 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 + 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 + 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 + 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 + 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 + void testGetPatternReturnsDefaultForEmptyOptionsArray() { + assertEquals( + DEFAULT_PATTERN, + DatePatternConverter.newInstance(Strings.EMPTY_ARRAY).getPattern()); + } + + @Test + void testGetPatternReturnsDefaultForInvalidPattern() { + final String[] invalid = {"A single `V` is not allow by `DateTimeFormatter` and should cause an exception"}; + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern()); + } + + @Test + void testGetPatternReturnsDefaultForNullOptions() { + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(null).getPattern()); + } + + @Test + void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() { + assertEquals( + DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern()); + } + + @Test + void testGetPatternReturnsDefaultForTwoNullElementsOptionsArray() { + assertEquals( + DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[2]).getPattern()); + } + + @Test + void testGetPatternReturnsNullForUnix() { + final String[] options = {"UNIX"}; + assertNull(DatePatternConverter.newInstance(options).getPattern()); + } + + @Test + void testGetPatternReturnsNullForUnixMillis() { + final String[] options = {"UNIX_MILLIS"}; + assertNull(DatePatternConverter.newInstance(options).getPattern()); + } + + @Test + void testNewInstanceAllowsNullParameter() { + DatePatternConverter.newInstance(null); // no errors + } + + private static final String[] PATTERN_NAMES = + Stream.of(NamedInstantPattern.values()).map(Enum::name).toArray(String[]::new); + + @Test + void testPredefinedFormatWithoutTimezone() { + for (final String patternName : PATTERN_NAMES) { + final String[] options = {patternName}; + final DatePatternConverter converter = DatePatternConverter.newInstance(options); + final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName); + assertEquals(expectedPattern, converter.getPattern()); + } + } + + @Test + void testPredefinedFormatWithTimezone() { + for (final String patternName : PATTERN_NAMES) { + final String[] options = {patternName, "PST"}; // Pacific Standard Time=UTC-8:00 + final DatePatternConverter converter = DatePatternConverter.newInstance(options); + final String expectedPattern = DatePatternConverter.decodeNamedPattern(patternName); + assertEquals(expectedPattern, converter.getPattern()); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.java new file mode 100644 index 00000000000..38116f4762e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.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.pattern; + +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingTestProperties; + +@SetTestProperty(key = "log4j2.enableThreadlocals", value = "true") +@UsingTestProperties +class DatePatternConverterWithThreadLocalsTest extends DatePatternConverterTestBase { + + DatePatternConverterWithThreadLocalsTest() { + super(true); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.java new file mode 100644 index 00000000000..1559e28efb5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.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.pattern; + +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.apache.logging.log4j.test.junit.UsingTestProperties; + +@SetTestProperty(key = "log4j2.enable.threadlocals", value = "false") +@UsingTestProperties +class DatePatternConverterWithoutThreadLocalsTest extends DatePatternConverterTestBase { + + DatePatternConverterWithoutThreadLocalsTest() { + super(false); + } +} 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..8cd93ccfb4b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.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.pattern; + +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 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.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j2-console-disableAnsi.xml") +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 + void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + 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..75e7215deda --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +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 { + + @Test + void testNullAndEmptyInputs() { + final DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create("1.1*"); + + assertDoesNotThrow(() -> abbreviator.abbreviate("orig", null)); + assertDoesNotThrow(() -> abbreviator.abbreviate(null, new StringBuilder())); + + final 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(final 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(final String pattern, final String input, final String expected) { + final DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create(pattern); + final StringBuilder actual = new StringBuilder(); + abbreviator.abbreviate(input, actual); + assertEquals(expected, actual.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..f8cd60d4d66 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.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.pattern; + +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 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.jupiter.api.Test; + +class EncodingPatternConverterTest { + + @Test + void testReplacement() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(EncodingPatternConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Test \r\n
this
&
that
")) + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final String[] options = new String[] {"%msg"}; + final EncodingPatternConverter converter = + EncodingPatternConverter.newInstance(ctx.getConfiguration(), options); + 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 + void testJsonEscaping() { + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(getClass().getName()) + .setLevel(Level.DEBUG) + .setMessage(new SimpleMessage( + "This string contains \"quotes\" and \\ backslash and \u001F control and\nnewline")) + .build(); + final String expected = + "This string contains \\\"quotes\\\" and \\\\ backslash and \\u001F control and\\nnewline"; + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final String[] options = new String[] {"%msg", "JSON"}; + final EncodingPatternConverter converter = + EncodingPatternConverter.newInstance(ctx.getConfiguration(), options); + + assertNotNull(converter, "Error creating converter"); + converter.format(event, sb); + + assertEquals(expected, sb.toString()); + } + + @Test + void testCrlfEscaping() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(EncodingPatternConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage( + new SimpleMessage("Test \r\n
this\r
& \n
that
")) + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final String[] options = new String[] {"%msg", "CRLF"}; + final EncodingPatternConverter converter = + EncodingPatternConverter.newInstance(ctx.getConfiguration(), options); + assertNotNull(converter, "Error creating converter"); + converter.format(event, sb); + assertEquals("Test \\r\\n
this\\r
& \\n
that
", sb.toString()); + } + + @Test + void testXmlEscaping() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(EncodingPatternConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Test \r\n
this
&
that
")) + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final String[] options = new String[] {"%msg", "XML"}; + final EncodingPatternConverter converter = + EncodingPatternConverter.newInstance(ctx.getConfiguration(), options); + 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 + 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-test/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 new file mode 100644 index 00000000000..835defe8eb8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class EndOfBatchPatternConverterTest { + + @Test + void testConverterTrue() { + final LogEvent event = Log4jLogEvent.newBuilder().setEndOfBatch(true).build(); + final StringBuilder sb = new StringBuilder(); + final LogEventPatternConverter converter = EndOfBatchPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("true", sb.toString()); + } + + @Test + void testConverterFalse() { + final LogEvent event = Log4jLogEvent.newBuilder().build(); + final StringBuilder sb = new StringBuilder(); + final LogEventPatternConverter converter = EndOfBatchPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("false", sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..969bda3b272 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.jupiter.api.Test; + +class EqualsIgnoreCaseReplacementConverterTest { + + @Test + void testMarkerReplacement() { + testReplacement("%marker", Strings.EMPTY); + } + + @Test + void testMarkerSimpleNameReplacement() { + testReplacement("%markerSimpleName", Strings.EMPTY); + } + + @Test + void testLoggerNameReplacement() { + testReplacement("%logger", "aaa[" + EqualsIgnoreCaseReplacementConverterTest.class.getName() + "]zzz"); + } + + private void testReplacement(final String tag, final String expectedValue) { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(EqualsIgnoreCaseReplacementConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("This is a test")) // + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + 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-test/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 new file mode 100644 index 00000000000..09e4d777484 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.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.core.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.jupiter.api.Test; + +class EqualsReplacementConverterTest { + + private static final String TEST_MESSAGE = "This is a test"; + + @Test + void testMarkerReplacement() { + testReplacement("%marker", Strings.EMPTY); + } + + @Test + void testMarkerSimpleNameReplacement() { + testReplacement("%markerSimpleName", Strings.EMPTY); + } + + @Test + void testLoggerNameReplacement() { + testReplacement("%logger", "[" + EqualsReplacementConverterTest.class.getName() + "]"); + } + + @Test + void testMarkerReplacementWithMessage() { + testReplacement(TEST_MESSAGE, new String[] {"[%marker]", "[]", "%msg"}); + } + + private void testReplacement(final String tag, final String expectedValue) { + final String[] options = new String[] {"[" + tag + "]", "[]", expectedValue}; + testReplacement(expectedValue, options); + } + + private void testReplacement(final String expectedValue, final String[] options) { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(EqualsReplacementConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage(TEST_MESSAGE)) // + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final EqualsReplacementConverter converter = + EqualsReplacementConverter.newInstance(ctx.getConfiguration(), options); + assertNotNull(converter); + converter.format(event, sb); + assertEquals(expectedValue, sb.toString()); + } + + @Test + void testParseSubstitutionWithPattern() { + testParseSubstitution("%msg", TEST_MESSAGE); + } + + @Test + void testParseSubstitutionWithoutPattern() { + final String substitution = "test"; + testParseSubstitution(substitution, substitution); + } + + @Test + void testParseSubstitutionEmpty() { + testParseSubstitution("", ""); + } + + @Test + void testParseSubstitutionWithWhiteSpaces() { + testParseSubstitution(" ", " "); + } + + private void testParseSubstitution(final String substitution, final String expected) { + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(EqualsReplacementConverterTest.class.getName()) + .setLevel(Level.DEBUG) + .setMessage(new SimpleMessage(TEST_MESSAGE)) + .build(); + final LoggerContext ctx = LoggerContext.getContext(); + final EqualsReplacementConverter converter = EqualsReplacementConverter.newInstance( + ctx.getConfiguration(), 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..ea71b625440 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.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.logging.log4j.core.pattern; + +import static java.util.Arrays.asList; +import static org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.THROWING_METHOD; + +import foo.TestFriendlyException; +import java.util.List; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.AbstractPropertyTest; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.AbstractStackTraceTest; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.DepthTestCase; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * {@link ExtendedThrowablePatternConverter} tests. + */ +class ExtendedThrowablePatternConverterTest { + + @Nested + class PropertyTest extends AbstractPropertyTest { + + PropertyTest() { + super("%xEx", THROWING_METHOD); + } + } + + private static final List EXPECTED_FULL_STACK_TRACE_LINES = asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT + " ~[?:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.(TestFriendlyException.java:0) ~[test-classes/:?]", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT + " ~[?:0]", + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]"); + + @Nested + class StackTraceTest extends AbstractStackTraceTest { + + StackTraceTest() { + super("%xEx"); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#fullStackTracePatterns") + void full_output_should_match(final String pattern) { + final String effectivePattern = patternPrefix + pattern; + assertStackTraceLines(null, effectivePattern, EXPECTED_FULL_STACK_TRACE_LINES); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_limited_output_should_match(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines(depthTestCase, pattern, EXPECTED_FULL_STACK_TRACE_LINES); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_1(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(foo)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT + " ~[?:?]", + " ... suppressed 2 lines", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT + " ~[?:0]", + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " ... suppressed 2 lines", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " ... suppressed 2 lines", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]")); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_2(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(bar)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT + " ~[?:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.(TestFriendlyException.java:0) ~[test-classes/:?]", + " ...", + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0) ~[test-classes/:?]", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]")); + } + } +} 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..0fa6a99a7ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Testing FormattingInfo. + */ +class FormattingInfoTest { + + @Test + 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 + 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 + 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..ca568695cf8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.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.pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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 java.util.stream.Stream; +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.test.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +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; + +/** + * Tests the HighlightConverter. + */ +class HighlightConverterTest { + + @Test + 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 + 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 + void testLevelNamesBad() { + final 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 + void testLevelNamesGood() { + final 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)); + } + + static Stream colors() { + return Stream.of( + Arguments.of("bright red", "\u001B[1;31m"), + Arguments.of("red bright", "\u001B[31;1m"), + Arguments.of("bright_red", "\u001B[91m"), + Arguments.of("#1cd42b", "\u001B[38;2;28;212;43m"), + Arguments.of("fg_bright_red bg_bright_blue bold", "\u001B[91;104;1m"), + Arguments.of("FG_#1cd42b BG_#000000", "\u001B[38;2;28;212;43;48;2;0;0;0m")); + } + + @ParameterizedTest + @MethodSource("colors") + void testColors(final String colorName, final String escape) { + final String[] options = { + "%-5level: %msg", + PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false, " + "INFO=" + + colorName + }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).build(); + final StringBuilder buffer = new StringBuilder(); + converter.format(event, buffer); + assertEquals(escape + "INFO : \u001B[m", buffer.toString()); + } + + @Test + void testLevelNamesUnknown() { + final 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 + 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 + @UsingStatusListener + void testNoAnsiEmpty(final ListStatusListener listener) { + final String[] options = {"", PatternParser.DISABLE_ANSI + "=true"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertThat(listener.findStatusData(Level.WARN)).isEmpty(); + + 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 + @UsingStatusListener + void testNoAnsiNonEmpty(final ListStatusListener listener) { + final String[] options = {"%-5level: %msg", PatternParser.DISABLE_ANSI + "=true"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertThat(listener.findStatusData(Level.WARN)).isEmpty(); + + 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()); + } + + /* + This test ensure that a keyvalue pair separated by = sign must be provided in order to configure the highlighting style + */ + @Test + @UsingStatusListener + void testBadStyleOption(final ListStatusListener listener) { + String defaultWarnColor = "yellow"; + String defaultInfoColor = "green"; + final String[] options = { + "%5level", + PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false, " + "LOGBACK" + }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + // As the default highlighting WARN color is Yellow while the LOGBACK color is Red + assertEquals(AnsiEscape.createSequence(defaultWarnColor), converter.getLevelStyle(Level.WARN)); + // As the default highlighting INFO color is Green while the LOGBACK color is Blue + assertEquals(AnsiEscape.createSequence(defaultInfoColor), converter.getLevelStyle(Level.INFO)); + assertThat(listener.findStatusData(Level.WARN)).hasSize(1); + } + + 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-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java new file mode 100644 index 00000000000..0595f68533d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.presentation.HexadecimalRepresentation.HEXA_REPRESENTATION; + +import java.util.Collections; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class JAnsiTextRendererTest { + + public static Stream testRendering() { + return Stream.of( + // Use style names + Arguments.of( + "KeyStyle=white ValueStyle=cyan,bold", + "@|KeyStyle key|@ = @|ValueStyle some value|@", + "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), + // Use AnsiEscape codes directly + Arguments.of( + "", + "@|white key|@ = @|cyan,bold some value|@", + "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), + // Return broken escapes as is + Arguments.of("", "Hello @|crazy|@ world!", "Hello @|crazy|@ world!"), + Arguments.of("", "Hello @|world!", "Hello @|world!")); + } + + @ParameterizedTest + @MethodSource + void testRendering(final String format, final String text, final String expected) { + final JAnsiTextRenderer renderer = new JAnsiTextRenderer(new String[] {"ansi", format}, Collections.emptyMap()); + final StringBuilder actual = new StringBuilder(); + renderer.render(new StringBuilder(text), actual); + assertThat(actual.toString()) + .as("Rendering text '%s'", text) + .withRepresentation(HEXA_REPRESENTATION) + .isEqualTo(expected); + } +} 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 76% 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..70ba0683bf1 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 @@ -1,46 +1,44 @@ /* * 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.logging.log4j.core.pattern; +import static org.junit.jupiter.api.Assertions.assertEquals; + 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.Message; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class LevelPatternConverterTest { +class LevelPatternConverterTest { private void testLevelLength(final int length, final String debug, final String warn) { final Message msg = new SimpleMessage("Hello"); LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); final StringBuilder sb = new StringBuilder(); LevelPatternConverter converter = LevelPatternConverter.newInstance(null); converter.format(event, sb); assertEquals(Level.DEBUG.toString(), sb.toString()); - final String[] opts = new String[] { "length=" + length }; + final String[] opts = new String[] {"length=" + length}; converter = LevelPatternConverter.newInstance(opts); sb.setLength(0); converter.format(event, sb); @@ -48,44 +46,46 @@ private void testLevelLength(final int length, final String debug, final String event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.WARN) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); sb.setLength(0); converter.format(event, sb); assertEquals(warn, sb.toString()); } @Test - public void testLevelLength1() { + void testLevelLength1() { testLevelLength(1, "D", "W"); } @Test - public void testLevelLength10() { + void testLevelLength10() { testLevelLength(10, "DEBUG", "WARN"); } @Test - public void testLevelLength2() { + void testLevelLength2() { testLevelLength(2, "DE", "WA"); } @Test - public void testLevelLength5() { + void testLevelLength5() { testLevelLength(5, "DEBUG", "WARN"); } @Test - public void testLevelLowerCase() { + void testLevelLowerCase() { final Message msg = new SimpleMessage("Hello"); LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); final StringBuilder sb = new StringBuilder(); LevelPatternConverter converter = LevelPatternConverter.newInstance(null); converter.format(event, sb); assertEquals(Level.DEBUG.toString(), sb.toString()); - final String[] opts = new String[] { "lowerCase=true" }; + final String[] opts = new String[] {"lowerCase=true"}; converter = LevelPatternConverter.newInstance(opts); sb.setLength(0); converter.format(event, sb); @@ -93,24 +93,26 @@ public void testLevelLowerCase() { event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.WARN) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); sb.setLength(0); converter.format(event, sb); assertEquals("warn", sb.toString()); } @Test - public void testLevelMap() { + void testLevelMap() { final Message msg = new SimpleMessage("Hello"); LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); final StringBuilder sb = new StringBuilder(); LevelPatternConverter converter = LevelPatternConverter.newInstance(null); converter.format(event, sb); assertEquals(Level.DEBUG.toString(), sb.toString()); - final String[] opts = new String[] { "WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info" }; + final String[] opts = new String[] {"WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info"}; converter = LevelPatternConverter.newInstance(opts); sb.setLength(0); converter.format(event, sb); @@ -118,24 +120,26 @@ public void testLevelMap() { event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.WARN) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); sb.setLength(0); converter.format(event, sb); assertEquals("Warning", sb.toString()); } @Test - public void testLevelMapWithLength() { + void testLevelMapWithLength() { final Message msg = new SimpleMessage("Hello"); LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); final StringBuilder sb = new StringBuilder(); LevelPatternConverter converter = LevelPatternConverter.newInstance(null); converter.format(event, sb); assertEquals(Level.DEBUG.toString(), sb.toString()); - final String[] opts = new String[] { "WARN=Warning, length=2" }; + final String[] opts = new String[] {"WARN=Warning, length=2"}; converter = LevelPatternConverter.newInstance(opts); sb.setLength(0); converter.format(event, sb); @@ -143,24 +147,26 @@ public void testLevelMapWithLength() { event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.WARN) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); sb.setLength(0); converter.format(event, sb); assertEquals("Warning", sb.toString()); } @Test - public void testLevelMapWithLengthAndLowerCase() { + void testLevelMapWithLengthAndLowerCase() { final Message msg = new SimpleMessage("Hello"); LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); final StringBuilder sb = new StringBuilder(); LevelPatternConverter converter = LevelPatternConverter.newInstance(null); converter.format(event, sb); assertEquals(Level.DEBUG.toString(), sb.toString()); - final String[] opts = new String[] { "WARN=Warning, length=2, lowerCase=true" }; + final String[] opts = new String[] {"WARN=Warning, length=2, lowerCase=true"}; converter = LevelPatternConverter.newInstance(opts); sb.setLength(0); converter.format(event, sb); @@ -168,10 +174,10 @@ public void testLevelMapWithLengthAndLowerCase() { event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.WARN) // - .setMessage(msg).build(); + .setMessage(msg) + .build(); sb.setLength(0); converter.format(event, sb); assertEquals("Warning", sb.toString()); } - } diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..d3fd0e05678 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Tests the LiteralPatternConverter. + */ +class LiteralPatternConverterTest { + + @Test + 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 + 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-test/src/test/java/org/apache/logging/log4j/core/pattern/LocationPatternConvertersRequireLocationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LocationPatternConvertersRequireLocationTest.java new file mode 100644 index 00000000000..8ca52574d59 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LocationPatternConvertersRequireLocationTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.apache.logging.log4j.core.impl.LocationAware; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class LocationPatternConvertersRequireLocationTest { + + /** + * Reproduces github issue #2781. + */ + @ParameterizedTest + @CsvSource({"%L", "%l", "%F", "%C", "%M"}) + void testThatLocationDependentPatternConvertersIndicateLocationRequirement(final String converterKey) { + final PatternFormatter formatter = createSinglePatternFormatterFromConverterKey(converterKey); + + final LocationAware locationAwareFormatter = assertInstanceOf(LocationAware.class, formatter.getConverter()); + assertTrue(locationAwareFormatter.requiresLocation()); + } + + private static PatternFormatter createSinglePatternFormatterFromConverterKey(final String converterKey) { + final PatternParser parser = new PatternParser(PatternConverter.CATEGORY); + final List formatters = parser.parse(converterKey); + assertEquals(1, formatters.size()); + final PatternFormatter formatter = formatters.get(0); + return formatter; + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..37eb4a68230 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class LoggerFqcnPatternConverterTest { + + private static final String FQCN = "com.acme.TheClass"; + + @Test + void testConverter() { + final LogEvent event = Log4jLogEvent.newBuilder().setLoggerFqcn(FQCN).build(); + final StringBuilder sb = new StringBuilder(); + final LogEventPatternConverter converter = LoggerFqcnPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals(FQCN, sb.toString()); + } +} 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..592e6d2daed --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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.MapMessage; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.jupiter.api.Test; + +class MapPatternConverterTest { + + @Test + 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 + 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 + 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-test/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 new file mode 100644 index 00000000000..d7431432af1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +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.Message; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link MarkerPatternConverter}. + */ +class MarkerPatternConverterTest { + + @Test + void testLookup() { + final Message msg = new StructuredDataMessage("Test", "This is a test", "Audit"); + final Marker eventMarker = MarkerManager.getMarker("EVENT"); + final Marker auditMarker = MarkerManager.getMarker("AUDIT").setParents(eventMarker); + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("MyLogger") + .setMarker(auditMarker) + .setLevel(Level.DEBUG) + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + final MarkerPatternConverter converter = MarkerPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals(auditMarker.toString(), sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..b5a49a7cb50 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +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.Message; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link MarkerSimpleNamePatternConverter}. + */ +class MarkerSimpleNamePatternConverterTest { + + @Test + void testLookup() { + final Message msg = new StructuredDataMessage("Test", "This is a test", "Audit"); + final Marker eventMarker = MarkerManager.getMarker("EVENT"); + final Marker auditMarker = MarkerManager.getMarker("AUDIT").setParents(eventMarker); + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("MyLogger") + .setMarker(auditMarker) + .setLevel(Level.DEBUG) + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + final MarkerSimpleNamePatternConverter converter = MarkerSimpleNamePatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals(auditMarker.getName(), sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ae958d187c2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +class MaxLengthConverterTest { + + private static final MaxLengthConverter converter = MaxLengthConverter.newInstance(null, new String[] {"%m", "10"}); + + @Test + void testUnderMaxLength() { + final Message message = new SimpleMessage("0123456789"); + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("MyLogger") + .setLevel(Level.DEBUG) + .setMessage(message) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("0123456789", sb.toString()); + } + + @Test + void testOverMaxLength() { + final Message message = new SimpleMessage("01234567890123456789"); + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("MyLogger") + .setLevel(Level.DEBUG) + .setMessage(message) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("0123456789", sb.toString()); + } + + @Test + void testOverMaxLength21WithEllipsis() { + final Message message = new SimpleMessage("012345678901234567890123456789"); + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("MyLogger") + .setLevel(Level.DEBUG) + .setMessage(message) + .build(); + final StringBuilder sb = new StringBuilder(); + MaxLengthConverter.newInstance(null, new String[] {"%m", "21"}).format(event, sb); + assertEquals("012345678901234567890...", sb.toString()); + } +} 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 77% 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..e06eea4bbfa 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 @@ -1,52 +1,45 @@ /* * 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.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.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.ThreadContextMapRule; 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.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * - */ -public class MdcPatternConverterTest { - - @Rule - public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule(); +@UsingThreadContextMap +class MdcPatternConverterTest { - @Before - public void setup() { + @BeforeEach + void setup() { ThreadContext.put("subject", "I"); ThreadContext.put("verb", "love"); ThreadContext.put("object", "Log4j"); } @Test - public void testConverter() { + void testConverter() { final Message msg = new SimpleMessage("Hello"); final MdcPatternConverter converter = MdcPatternConverter.newInstance(null); final LogEvent event = Log4jLogEvent.newBuilder() // @@ -58,11 +51,11 @@ 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 - public void testConverterWithExistingData() { + void testConverterWithExistingData() { final Message msg = new SimpleMessage("Hello"); final MdcPatternConverter converter = MdcPatternConverter.newInstance(null); final LogEvent event = Log4jLogEvent.newBuilder() // @@ -75,10 +68,11 @@ 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() { + void testConverterFullEmpty() { ThreadContext.clearMap(); final Message msg = new SimpleMessage("Hello"); final MdcPatternConverter converter = MdcPatternConverter.newInstance(null); @@ -91,11 +85,11 @@ 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 - public void testConverterFullSingle() { + void testConverterFullSingle() { ThreadContext.clearMap(); ThreadContext.put("foo", "bar"); final Message msg = new SimpleMessage("Hello"); @@ -109,13 +103,13 @@ 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 - public void testConverterWithKey() { + void testConverterWithKey() { final Message msg = new SimpleMessage("Hello"); - final String [] options = new String[] {"object"}; + final String[] options = new String[] {"object"}; final MdcPatternConverter converter = MdcPatternConverter.newInstance(options); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -130,9 +124,9 @@ public void testConverterWithKey() { } @Test - public void testConverterWithKeys() { + void testConverterWithKeys() { final Message msg = new SimpleMessage("Hello"); - final String [] options = new String[] {"object, subject"}; + final String[] options = new String[] {"object, subject"}; final MdcPatternConverter converter = MdcPatternConverter.newInstance(options); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -147,9 +141,9 @@ public void testConverterWithKeys() { } @Test - public void testConverterWithKeysAndPrefix() { + void testConverterWithKeysAndPrefix() { final Message msg = new SimpleMessage("Hello"); - final String [] options = new String[] {"object, subject"}; + final String[] options = new String[] {"object, subject"}; final MdcPatternConverter converter = MdcPatternConverter.newInstance(options); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -165,9 +159,9 @@ public void testConverterWithKeysAndPrefix() { } @Test - public void testConverterWithKeysSinglePresent() { + void testConverterWithKeysSinglePresent() { final Message msg = new SimpleMessage("Hello"); - final String [] options = new String[] {"object, notpresent"}; + final String[] options = new String[] {"object, notpresent"}; final MdcPatternConverter converter = MdcPatternConverter.newInstance(options); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -182,10 +176,10 @@ public void testConverterWithKeysSinglePresent() { } @Test - public void testConverterWithKeysNonePresent() { + void testConverterWithKeysNonePresent() { ThreadContext.clearMap(); final Message msg = new SimpleMessage("Hello"); - final String [] options = new String[] {"object, subject"}; + final String[] options = new String[] {"object, subject"}; final MdcPatternConverter converter = MdcPatternConverter.newInstance(options); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -198,6 +192,4 @@ public void testConverterWithKeysNonePresent() { final String expected = "{}"; assertEquals(expected, str); } - } - diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.java new file mode 100644 index 00000000000..09e3a1bd0ac --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageAnsiConverterTest.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.pattern; + +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 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.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-message-ansi.xml") +class MessageAnsiConverterTest { + + 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 + void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + void testReplacement() { + // See https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html + 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)); + // System.out.println(msgs.get(0)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ba58e2ad3d2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.DefaultConfiguration; +import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.junit.jupiter.api.Test; + +class MessagePatternConverterTest { + + @Test + void testPattern() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); + Message msg = new SimpleMessage("Hello!"); + LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + 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(0, sb.length(), "Incorrect length: " + sb); + msg = new SimpleMessage(null); + event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + sb = new StringBuilder(); + converter.format(event, sb); + assertEquals(4, sb.length(), "Incorrect length: " + sb); + } + + @Test + void testPatternAndParameterizedMessageDateLookup() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); + final Message msg = new ParameterizedMessage("${date:now:buhu}"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("${date:now:buhu}", sb.toString(), "Unexpected result"); + } + + @Test + void testDefaultDisabledLookup() { + final Configuration config = + new DefaultConfigurationBuilder().addProperty("foo", "bar").build(true); + final MessagePatternConverter converter = MessagePatternConverter.newInstance(config, null); + 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 + void testDisabledLookup() { + final Configuration config = + new DefaultConfigurationBuilder().addProperty("foo", "bar").build(true); + final MessagePatternConverter converter = + MessagePatternConverter.newInstance(config, new String[] {"nolookups"}); + 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(), "Expected the raw pattern string without lookup"); + } + + @Test + 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 + void testPatternWithConfiguration() { + final Configuration config = new DefaultConfiguration(); + final MessagePatternConverter converter = MessagePatternConverter.newInstance(config, null); + Message msg = new SimpleMessage("Hello!"); + LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + 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(0, sb.length(), "Incorrect length: " + sb); + msg = new SimpleMessage(null); + event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + sb = new StringBuilder(); + converter.format(event, sb); + assertEquals(4, sb.length(), "Incorrect length: " + sb); + } + + @Test + void testMapMessageFormatJson() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[] {"json"}); + final Message msg = new StringMapMessage().with("key", "val"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("{\"key\":\"val\"}", sb.toString(), "Unexpected result"); + } + + @Test + void testMapMessageFormatXml() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[] {"xml"}); + final Message msg = new StringMapMessage().with("key", "val"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("\n val\n", sb.toString(), "Unexpected result"); + } + + @Test + void testMapMessageFormatDefault() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); + final Message msg = new StringMapMessage().with("key", "val"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("key=\"val\"", sb.toString(), "Unexpected result"); + } + + @Test + void testStructuredDataFormatFull() { + final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[] {"FULL"}); + final Message msg = new StructuredDataMessage("id", "message", "type").with("key", "val"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + 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..317c11b78fb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.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.pattern; + +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 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.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-message-styled.xml") +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 + void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + void testReplacement() { + // See https://www.javadoc.io/doc/org.jline/jline/latest/org/jline/jansi/AnsiRenderer.html + 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..0f4bb16072c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collection; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for {@link NameAbbreviator} which abbreviates dot-delimited strings such as logger and class names. + */ +class NameAbbreviatorTest { + + 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"} + }); + } + + @MethodSource("data") + @ParameterizedTest(name = "pattern=\"{0}\", expected={1}") + void testAbbreviatorPatterns(final String pattern, final String expected) { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(pattern); + final StringBuilder destination = new StringBuilder(); + abbreviator.abbreviate(this.getClass().getName(), destination); + final String actual = destination.toString(); + assertEquals(expected, actual); + } + + @MethodSource("data") + @ParameterizedTest(name = "pattern=\"{0}\", expected={1}") + void testAbbreviatorPatternsAppendLongPrefix(final String pattern, final String expected) { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(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); + } + + @MethodSource("data") + @ParameterizedTest(name = "pattern=\"{0}\", expected={1}") + void testAbbreviatorPatternsAppend(final String pattern, final String expected) { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(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-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java new file mode 100644 index 00000000000..bbe5e6e45eb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.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 static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class NamedInstantPatternTest { + + @ParameterizedTest + @EnumSource(NamedInstantPattern.class) + void compatibilityOfLegacyPattern(NamedInstantPattern namedPattern) { + InstantPatternFormatter legacyFormatter = InstantPatternFormatter.newBuilder() + .setPattern(namedPattern.getLegacyPattern()) + .setLegacyFormattersEnabled(true) + .build(); + InstantPatternFormatter formatter = InstantPatternFormatter.newBuilder() + .setPattern(namedPattern.getPattern()) + .setLegacyFormattersEnabled(false) + .build(); + Instant javaTimeInstant = Instant.now(); + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(javaTimeInstant.getEpochSecond(), javaTimeInstant.getNano()); + assertThat(legacyFormatter.format(instant)).isEqualTo(formatter.format(instant)); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..61ea54b481f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class NanoTimePatternConverterTest { + + @Test + void testConverterAppendsLogEventNanoTimeToStringBuilder() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setNanoTime(1234567) + .build(); + final StringBuilder sb = new StringBuilder(); + final NanoTimePatternConverter converter = NanoTimePatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("1234567", sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ea35af47820 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.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.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; +import org.junit.jupiter.api.Test; + +@UsingThreadContextStack +class NdcPatternConverterTest { + + @Test + void testEmpty() { + testConverter("[]"); + } + + @Test + void test1() { + ThreadContext.push("foo"); + testConverter("[foo]"); + } + + @Test + void test2() { + ThreadContext.push("foo"); + ThreadContext.push("bar"); + testConverter("[foo, bar]"); + } + + @Test + void test3() { + ThreadContext.push("foo"); + ThreadContext.push("bar"); + ThreadContext.push("baz"); + testConverter("[foo, bar, baz]"); + } + + private void testConverter(final String expected) { + final Message msg = new SimpleMessage("Hello"); + final NdcPatternConverter converter = NdcPatternConverter.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(); + assertEquals(expected, str); + } +} 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..519aa9c5fd1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.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.pattern; + +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 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.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j2-console-noConsoleNoAnsi.xml") +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 + void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + 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/PatternParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java new file mode 100644 index 00000000000..f493e25bb6a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Calendar; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +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.Configuration; +import org.apache.logging.log4j.core.config.NullConfiguration; +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.message.SimpleMessage; +import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PatternParserTest { + + static String OUTPUT_FILE = "output/PatternParser"; + static String WITNESS_FILE = "witness/PatternParser"; + LoggerContext ctx = LoggerContext.getContext(); + Logger root = ctx.getRootLogger(); + + private static final String msgPattern = "%m%n"; + private final String mdcMsgPattern1 = "%m : %X%n"; + private final String mdcMsgPattern2 = "%m : %X{key1}%n"; + private final String mdcMsgPattern3 = "%m : %X{key2}%n"; + private final String mdcMsgPattern4 = "%m : %X{key3}%n"; + private final String mdcMsgPattern5 = "%m : %X{key1},%X{key2},%X{key3}%n"; + private final String deeplyNestedPattern = "%notEmpty{ %maxLen{%X{var}}{3} }"; + + private static final String badPattern = "[%d{yyyyMMdd HH:mm:ss,SSS] %-5p [%c{10}] - %m%n"; + private static final String customPattern = "[%d{yyyyMMdd HH:mm:ss,SSS}] %-5p [%-25.25c{1}:%-4L] - %m%n"; + private static final String patternTruncateFromEnd = "%d; %-5p %5.-5c %m%n"; + private static final String patternTruncateFromBeginning = "%d; %-5p %5.5c %m%n"; + private static final String nestedPatternHighlight = + "%highlight{%d{dd MMM yyyy HH:mm:ss,SSS}{GMT+0} [%t] %-5level: %msg%n%throwable}"; + + private static final String KEY = "Converter"; + private PatternParser parser; + + @BeforeEach + 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( + pc.getName(), name, "Incorrect converter " + pc.getName() + " at index " + index + " expected " + name); + } + + /** + * Test the default pattern + */ + @Test + void defaultPattern() { + final List formatters = parser.parse(msgPattern); + assertNotNull(formatters); + assertEquals(2, formatters.size()); + validateConverter(formatters, 0, "Message"); + validateConverter(formatters, 1, "Line Sep"); + } + + /** + * Test the custom pattern + */ + @Test + void testCustomPattern() { + final List formatters = parser.parse(customPattern); + assertNotNull(formatters); + final StringMap mdc = ContextDataFactory.createContextData(); + mdc.putValue("loginId", "Fred"); + final Throwable t = new Throwable(); + final StackTraceElement[] elements = t.getStackTrace(); + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("org.apache.logging.log4j.PatternParserTest") // + .setMarker(MarkerManager.getMarker("TEST")) // + .setLoggerFqcn(Logger.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world")) // + .setContextData(mdc) // + .setThreadName("Thread1") // + .setSource(elements[0]) + .setTimeMillis(System.currentTimeMillis()) + .build(); + final StringBuilder buf = new StringBuilder(); + for (final PatternFormatter formatter : formatters) { + formatter.format(event, buf); + } + final String str = buf.toString(); + final String expected = "INFO [PatternParserTest :101 ] - Hello, world" + Strings.LINE_SEPARATOR; + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); + } + + @Test + void testPatternTruncateFromBeginning() { + final List formatters = parser.parse(patternTruncateFromBeginning); + assertNotNull(formatters); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("org.apache.logging.log4j.PatternParserTest") // + .setLoggerFqcn(Logger.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world")) // + .setThreadName("Thread1") // + .setTimeMillis(System.currentTimeMillis()) // + .build(); + final StringBuilder buf = new StringBuilder(); + for (final PatternFormatter formatter : formatters) { + formatter.format(event, buf); + } + final String str = buf.toString(); + final String expected = "INFO rTest Hello, world" + Strings.LINE_SEPARATOR; + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); + } + + @Test + void testPatternTruncateFromEnd() { + final List formatters = parser.parse(patternTruncateFromEnd); + assertNotNull(formatters); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("org.apache.logging.log4j.PatternParserTest") // + .setLoggerFqcn(Logger.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world")) // + .setThreadName("Thread1") // + .setTimeMillis(System.currentTimeMillis()) // + .build(); + final StringBuilder buf = new StringBuilder(); + for (final PatternFormatter formatter : formatters) { + formatter.format(event, buf); + } + final String str = buf.toString(); + final String expected = "INFO org.a Hello, world" + Strings.LINE_SEPARATOR; + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); + } + + @Test + void testBadPattern() { + final Calendar cal = Calendar.getInstance(); + cal.set(2001, Calendar.FEBRUARY, 3, 4, 5, 6); + cal.set(Calendar.MILLISECOND, 789); + final long timestamp = cal.getTimeInMillis(); + + final List formatters = parser.parse(badPattern); + assertNotNull(formatters); + final Throwable t = new Throwable(); + final StackTraceElement[] elements = t.getStackTrace(); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("a.b.c") // + .setLoggerFqcn(Logger.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world")) // + .setThreadName("Thread1") // + .setSource(elements[0]) // + .setTimeMillis(timestamp) // + .build(); + final StringBuilder buf = new StringBuilder(); + for (final PatternFormatter formatter : formatters) { + formatter.format(event, buf); + } + final String str = buf.toString(); + + // eats all characters until the closing '}' character + final String expected = "[2001-02-03 04:05:06,789] - Hello, world"; + assertTrue(str.startsWith(expected), "Expected to start with: " + expected + ". Actual: " + str); + } + + @Test + void testNestedPatternHighlight() { + testNestedPatternHighlight(Level.TRACE, "\u001B[30m"); + testNestedPatternHighlight(Level.DEBUG, "\u001B[36m"); + testNestedPatternHighlight(Level.INFO, "\u001B[32m"); + testNestedPatternHighlight(Level.WARN, "\u001B[33m"); + testNestedPatternHighlight(Level.ERROR, "\u001B[1;31m"); + testNestedPatternHighlight(Level.FATAL, "\u001B[1;31m"); + } + + private void testNestedPatternHighlight(final Level level, final String expectedStart) { + final List formatters = parser.parse(nestedPatternHighlight); + assertNotNull(formatters); + final Throwable t = new Throwable(); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("org.apache.logging.log4j.PatternParserTest") // + .setMarker(MarkerManager.getMarker("TEST")) // + .setLoggerFqcn(Logger.class.getName()) // + .setLevel(level) // + .setMessage(new SimpleMessage("Hello, world")) // + .setThreadName("Thread1") // + .setSource(/*stackTraceElement[0]*/ null) // + .setTimeMillis(System.currentTimeMillis()) // + .build(); + final StringBuilder buf = new StringBuilder(); + for (final PatternFormatter formatter : formatters) { + formatter.format(event, buf); + } + final String str = buf.toString(); + final String expectedEnd = String.format("] %-5s: Hello, world%s\u001B[m", level, Strings.LINE_SEPARATOR); + assertTrue(str.startsWith(expectedStart), "Expected to start with: " + expectedStart + ". Actual: " + str); + assertTrue(str.endsWith(expectedEnd), "Expected to end with: \"" + expectedEnd + "\". Actual: \"" + str); + } + + @Test + void testNanoPatternShort() { + testFirstConverter("%N", NanoTimePatternConverter.class); + } + + @Test + void testNanoPatternLong() { + testFirstConverter("%nano", NanoTimePatternConverter.class); + } + + @Test + void testThreadNamePattern() { + testThreadNamePattern("%thread"); + } + + @Test + void testThreadNameFullPattern() { + testThreadNamePattern("%threadName"); + } + + @Test + void testThreadIdFullPattern() { + testThreadIdPattern("%threadId"); + } + + @Test + void testThreadIdShortPattern1() { + testThreadIdPattern("%tid"); + } + + @Test + void testThreadIdShortPattern2() { + testThreadIdPattern("%T"); + } + + @Test + void testThreadPriorityShortPattern() { + testThreadPriorityPattern("%tp"); + } + + @Test + void testThreadPriorityFullPattern() { + testThreadPriorityPattern("%threadPriority"); + } + + @Test + void testLoggerFqcnPattern() { + testFirstConverter("%fqcn", LoggerFqcnPatternConverter.class); + } + + @Test + void testEndOfBatchPattern() { + testFirstConverter("%endOfBatch", EndOfBatchPatternConverter.class); + } + + private void testThreadIdPattern(final String pattern) { + testFirstConverter(pattern, ThreadIdPatternConverter.class); + } + + private void testThreadNamePattern(final String pattern) { + testFirstConverter(pattern, ThreadNamePatternConverter.class); + } + + private void testThreadPriorityPattern(final String pattern) { + testFirstConverter(pattern, ThreadPriorityPatternConverter.class); + } + + private void testFirstConverter(final String pattern, final Class checkClass) { + final List formatters = parser.parse(pattern); + assertNotNull(formatters); + final String msg = formatters.toString(); + assertEquals(1, formatters.size(), msg); + assertTrue(checkClass.isInstance(formatters.get(0).getConverter()), msg); + } + + @Test + void testThreadNameShortPattern() { + testThreadNamePattern("%t"); + } + + @Test + void testNanoPatternShortChangesConfigurationNanoClock() { + final Configuration config = new NullConfiguration(); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + final PatternParser pp = new PatternParser(config, KEY, null); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + pp.parse("%m"); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + pp.parse("%nano"); // this changes the config clock + assertInstanceOf(SystemNanoClock.class, config.getNanoClock()); + } + + @Test + void testNanoPatternLongChangesNanoClockFactoryMode() { + final Configuration config = new NullConfiguration(); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + final PatternParser pp = new PatternParser(config, KEY, null); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + pp.parse("%m"); + assertInstanceOf(DummyNanoClock.class, config.getNanoClock()); + + pp.parse("%N"); + assertInstanceOf(SystemNanoClock.class, config.getNanoClock()); + } + + @Test + void testDeeplyNestedPattern() { + final List formatters = parser.parse(deeplyNestedPattern); + assertNotNull(formatters); + assertEquals(1, formatters.size()); + + final StringMap mdc = ContextDataFactory.createContextData(); + mdc.putValue("var", "1234"); + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setContextData(mdc) + .build(); + final StringBuilder buf = new StringBuilder(); + formatters.get(0).format(event, buf); + final String expected = " 123 "; + assertEquals(expected, buf.toString()); + } + + @Test + void testMissingClosingBracket() { + testFirstConverter("%d{", DatePatternConverter.class); + } + + @Test + void testClosingBracketButWrongPlace() { + final List formatters = parser.parse("}%d{"); + assertNotNull(formatters); + assertEquals(2, formatters.size()); + + validateConverter(formatters, 0, "SimpleLiteral"); + validateConverter(formatters, 1, "Date"); + } + + @Test + void testExceptionWithFilters() { + final List formatters = + parser.parse("%d{DEFAULT} - %msg - %xEx{full}{filters(org.junit,org.eclipse)}%n"); + assertNotNull(formatters); + assertEquals(6, formatters.size()); + final PatternFormatter patternFormatter = formatters.get(4); + final LogEventPatternConverter converter = patternFormatter.getConverter(); + assertEquals(ExtendedThrowablePatternConverter.class, converter.getClass()); + final ExtendedThrowablePatternConverter exConverter = (ExtendedThrowablePatternConverter) converter; + final ThrowableFormatOptions options = exConverter.getOptions(); + assertTrue(options.getIgnorePackages().contains("org.junit")); + assertTrue(options.getIgnorePackages().contains("org.eclipse")); + assertEquals(System.lineSeparator(), options.getSeparator()); + } + + @Test + void testExceptionWithFiltersAndSeparator() { + final List formatters = + parser.parse("%d{DEFAULT} - %msg - %xEx{full}{filters(org.junit,org.eclipse)}{separator(|)}%n"); + assertNotNull(formatters); + assertEquals(6, formatters.size()); + final PatternFormatter patternFormatter = formatters.get(4); + final LogEventPatternConverter converter = patternFormatter.getConverter(); + assertEquals(ExtendedThrowablePatternConverter.class, converter.getClass()); + final ExtendedThrowablePatternConverter exConverter = (ExtendedThrowablePatternConverter) converter; + 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()); + } + + // LOG4J2-2564: Multiple newInstance methods. + @Test + void testMapPatternConverter() { + final List formatters = parser.parse("%K"); + assertNotNull(formatters); + assertEquals(1, formatters.size()); + final PatternFormatter formatter = formatters.get(0); + assertInstanceOf(MapPatternConverter.class, formatter.getConverter(), "Expected a MapPatternConverter"); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..39a9311f8e8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +/** + * Tests that process ID succeeds. + */ +class ProcessIdPatternConverterTest { + @Test + void getProcessId() { + final String[] defaultValue = {"???"}; + final String actual = + ProcessIdPatternConverter.newInstance(defaultValue).getProcessId(); + assertNotEquals("???", actual); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..a98fd1964ee --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.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.jupiter.api.Test; + +class RegexReplacementConverterTest { + + @Test + void testReplacement() { + ThreadContext.put("MyKey", "Apache"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(RegexReplacementConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("This is a test")) // + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + final String[] options = new String[] {"%logger %msg%n", "\\.", "/"}; + 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..2647869530b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.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.pattern; + +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 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.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-replace.xml") +@UsingThreadContextMap +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 + 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)); + } + + @Test + void testMessageReplacement() { + ThreadContext.put("MyKey", "Apache"); + logger.error("This is a test for ${ctx:MyKey}"); + final 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 + 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..8a6429628ab --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + +/** + * Tests that process ID succeeds. + */ +class RepeatPatternConverterTest { + @Test + void repeat() { + final String[] args = {"*", "10"}; + final String expected = "**********"; + final PatternConverter converter = RepeatPatternConverter.newInstance(null, args); + assertNotNull(converter, "No RepeatPatternConverter returned"); + final StringBuilder sb = new StringBuilder(); + converter.format(null, sb); + assertEquals(expected, sb.toString()); + sb.setLength(0); + final 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-test/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 new file mode 100644 index 00000000000..b7cee50b3ae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.util.Arrays.asList; +import static org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.EXCEPTION; +import static org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.LEVEL; +import static org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.convert; +import static org.assertj.core.api.Assertions.assertThat; + +import foo.TestFriendlyException; +import java.util.List; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.AbstractPropertyTest; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.AbstractStackTraceTest; +import org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest.DepthTestCase; +import org.apache.logging.log4j.core.util.Throwables; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * {@link RootThrowablePatternConverter} tests. + */ +class RootThrowablePatternConverterTest { + + private static final StackTraceElement THROWING_METHOD = + Throwables.getRootCause(EXCEPTION).getStackTrace()[0]; + + @Nested + class PropertyTest extends AbstractPropertyTest { + + PropertyTest() { + super("%rEx", THROWING_METHOD); + } + } + + private static final List EXPECTED_FULL_STACK_TRACE_LINES = asList( + "[CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Wrapped by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + " Wrapped by: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.(TestFriendlyException.java:0)", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT, + " Suppressed: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Wrapped by: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more"); + + @Nested + class StackTraceTest extends AbstractStackTraceTest { + + StackTraceTest() { + super("%rEx"); + } + + @Test + @Override + void output_should_be_newline_prefixed() { + final String pattern = "%p" + patternPrefix; + final String stackTrace = convert(pattern); + final String expectedStart = String.format( + "%s%n[CIRCULAR REFERENCE: %s", LEVEL, EXCEPTION.getClass().getCanonicalName()); + assertThat(stackTrace).as("pattern=`%s`", pattern).startsWith(expectedStart); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#fullStackTracePatterns") + void full_output_should_match(final String pattern) { + final String effectivePattern = patternPrefix + pattern; + assertStackTraceLines(null, effectivePattern, EXPECTED_FULL_STACK_TRACE_LINES); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_limited_output_should_match(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines(depthTestCase, pattern, EXPECTED_FULL_STACK_TRACE_LINES); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_1(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(foo)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "[CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Wrapped by: foo.TestFriendlyException: r_c_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r_c [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + " Wrapped by: foo.TestFriendlyException: r_c_s [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " ... suppressed 2 lines", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT, + " Suppressed: foo.TestFriendlyException: r_s_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + " Wrapped by: foo.TestFriendlyException: r_s [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " ... suppressed 2 lines", + " ... 3 more")); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_2(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(bar)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "[CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Wrapped by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + " Wrapped by: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Wrapped by: foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.(TestFriendlyException.java:0)", + " ...", + " Suppressed: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Wrapped by: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more")); + } + } +} 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..05e2c53c3de --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.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.pattern; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +import java.util.List; +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.Test; + +@LoggerContextSource("SequenceNumberPatternConverterTest.yaml") +class SequenceNumberPatternConverterTest { + + @Test + 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..ccc342dfb17 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +import java.util.List; +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.Test; + +@LoggerContextSource("SequenceNumberPatternConverterZeroPaddedTest.yaml") +class SequenceNumberPatternConverterZeroPaddedTest { + + @Test + 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..c64c0638f94 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class SimpleLiteralPatternConverterTest { + + @Test + void testConvertBackslashes() { + final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + final LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, true); + final String actual = literal(converter); + assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", actual); + } + + @Test + void testDontConvertBackslashes() { + final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + final LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, false); + final String actual = literal(converter); + assertEquals(literal, actual); + } + + private static String literal(final LogEventPatternConverter converter) { + final 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..6b03c699054 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.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.pattern; + +import static org.assertj.core.api.Assertions.assertThat; +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 java.util.List; +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.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +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.ListStatusListener; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +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; + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable + } + + @Test + @LoggerContextSource("log4j-style.xml") + 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 + void testNull() { + assertNull(StyleConverter.newInstance(null, null)); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.HighlightConverterTest#colors") + void testHighlightConverterCompatibility(final String color, final String escape) { + final StyleConverter converter = StyleConverter.newInstance(null, new String[] {"Hello!", color}); + final StringBuilder sb = new StringBuilder(); + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).build(); + converter.format(event, sb); + assertEquals(escape + "Hello!" + AnsiEscape.getDefaultStyle(), sb.toString()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.HighlightConverterTest#colors") + void testLegacyCommaSeparator(final String color, final String escape) { + final StyleConverter converter = + StyleConverter.newInstance(null, new String[] {"Hello!", color.replaceAll("\\s+", ",")}); + final StringBuilder sb = new StringBuilder(); + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).build(); + converter.format(event, sb); + assertEquals(escape + "Hello!" + AnsiEscape.getDefaultStyle(), sb.toString()); + } + + @Test + @UsingStatusListener + void testNoAnsiNoWarnings(final ListStatusListener listener) { + StyleConverter converter = StyleConverter.newInstance(null, new String[] {"", "disableAnsi=true"}); + assertThat(converter).isNotNull(); + converter = StyleConverter.newInstance(null, new String[] {"", "noConsoleNoAnsi=true"}); + assertThat(converter).isNotNull(); + converter = StyleConverter.newInstance(null, new String[] {"", "INVALID_STYLE"}); + assertThat(converter).isNotNull(); + assertThat(listener.findStatusData(Level.WARN)) + .hasSize(1) + .extracting(data -> data.getMessage().getFormattedMessage()) + .containsExactly("The style attribute INVALID_STYLE is incorrect."); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..69a7d9d2a2a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class ThreadIdPatternConverterTest { + + @Test + void testConverterAppendsLogEventNanoTimeToStringBuilder() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setThreadId(1) + .build(); + final StringBuilder sb = new StringBuilder(); + final ThreadIdPatternConverter converter = ThreadIdPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("1", sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..545766db5b9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class ThreadNamePatternConverterTest { + + @Test + void testConverterAppendsLogEventNanoTimeToStringBuilder() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setThreadName("Hello-1") + .build(); + final StringBuilder sb = new StringBuilder(); + final ThreadNamePatternConverter converter = ThreadNamePatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("Hello-1", sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..fd27bbb2ef7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.junit.jupiter.api.Test; + +class ThreadPriorityPatternConverterTest { + + @Test + void testConverterAppendsLogEventNanoTimeToStringBuilder() { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setThreadPriority(1) + .build(); + final StringBuilder sb = new StringBuilder(); + final ThreadPriorityPatternConverter converter = ThreadPriorityPatternConverter.newInstance(null); + converter.format(event, sb); + assertEquals("1", sb.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..e178526fd99 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 java.util.Arrays.asList; +import static org.apache.logging.log4j.util.Strings.LINE_SEPARATOR; +import static org.assertj.core.api.Assertions.assertThat; + +import foo.TestFriendlyException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +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.core.layout.PatternLayout; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * {@link ThrowablePatternConverter} tests. + */ +public class ThrowablePatternConverterTest { + + static final Throwable EXCEPTION = TestFriendlyException.INSTANCE; + + static final StackTraceElement THROWING_METHOD = EXCEPTION.getStackTrace()[0]; + + private static final PatternParser PATTERN_PARSER = PatternLayout.createPatternParser(null); + + static final Level LEVEL = Level.FATAL; + + static final class SeparatorTestCase { + + final String patternAddendum; + + private final String conversionEnding; + + private SeparatorTestCase(final String patternAddendum, final String conversionEnding) { + this.patternAddendum = patternAddendum; + this.conversionEnding = conversionEnding; + } + + @Override + public String toString() { + return String.format("{patternAddendum=`%s`, conversionEnding=`%s`}", patternAddendum, conversionEnding); + } + } + + static Stream separatorTestCases() { + final String level = LEVEL.toString(); + return Stream.of( + // Only separators + new SeparatorTestCase("{separator()}", ""), + new SeparatorTestCase("{separator(#)}", "#"), + // Only suffixes + new SeparatorTestCase("{suffix()}", LINE_SEPARATOR), + new SeparatorTestCase("{suffix(~)}", " ~" + LINE_SEPARATOR), + new SeparatorTestCase("{suffix(%level)}", " " + level + LINE_SEPARATOR), + new SeparatorTestCase("{suffix(%rEx)}", LINE_SEPARATOR), + // Both separators and suffixes + new SeparatorTestCase("{separator()}{suffix()}", ""), + new SeparatorTestCase("{separator()}{suffix(~)}", " ~"), + new SeparatorTestCase("{separator()}{suffix(%level)}", " " + level), + new SeparatorTestCase("{separator()}{suffix(%rEx)}", ""), + new SeparatorTestCase("{separator(#)}{suffix()}", "#"), + new SeparatorTestCase("{separator(#)}{suffix(~)}", " ~#"), + new SeparatorTestCase("{separator(#)}{suffix(%level)}", " " + level + "#"), + new SeparatorTestCase("{separator(#)}{suffix(%rEx)}", "#")); + } + + @Nested + class PropertyTest extends AbstractPropertyTest { + + PropertyTest() { + super("%ex", THROWING_METHOD); + } + } + + abstract static class AbstractPropertyTest { + + private final String patternPrefix; + + private final StackTraceElement throwingMethod; + + AbstractPropertyTest(final String patternPrefix, final StackTraceElement throwingMethod) { + this.patternPrefix = patternPrefix; + this.throwingMethod = throwingMethod; + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void message_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.message}", EXCEPTION.getMessage()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void localizedMessage_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.localizedMessage}", EXCEPTION.getLocalizedMessage()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void className_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.className}", throwingMethod.getClassName()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void methodName_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.methodName}", throwingMethod.getMethodName()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void lineNumber_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.lineNumber}", throwingMethod.getLineNumber() + ""); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void fileName_should_be_rendered(final SeparatorTestCase separatorTestCase) { + assertConversion(separatorTestCase, "{short.fileName}", throwingMethod.getFileName()); + } + + private void assertConversion( + final SeparatorTestCase separatorTestCase, final String pattern, final Object expectedOutput) { + final String effectivePattern = patternPrefix + pattern + separatorTestCase.patternAddendum; + final String output = convert(effectivePattern); + assertThat(output) + .as("pattern=`%s`, separatorTestCase=%s", pattern, separatorTestCase) + .isEqualTo(expectedOutput); + } + } + + static final class DepthTestCase { + + final SeparatorTestCase separatorTestCase; + + final int maxLineCount; + + private DepthTestCase(final SeparatorTestCase separatorTestCase, final int maxLineCount) { + this.separatorTestCase = separatorTestCase; + this.maxLineCount = maxLineCount; + } + + @Override + public String toString() { + return String.format("{separatorTestCase=%s, maxLineCount=%d}", separatorTestCase, maxLineCount); + } + } + + static Stream depthTestCases() { + return separatorTestCases().flatMap(separatorTestCase -> maxLineCounts() + .map(maxLineCount -> new DepthTestCase(separatorTestCase, maxLineCount))); + } + + static Stream maxLineCounts() { + return Stream.of(0, 1, 2, 3, 4, 5, 10, 15, 20, Integer.MAX_VALUE); + } + + static Stream fullStackTracePatterns() { + return Stream.of("", "{}", "{full}", "{" + Integer.MAX_VALUE + "}", "{separator(" + LINE_SEPARATOR + ")}"); + } + + @Nested + class StackTraceTest extends AbstractStackTraceTest { + + StackTraceTest() { + super("%ex"); + } + + // This test does not provide `separator` and `suffix` options, since the reference output will be obtained from + // `Throwable#printStackTrace()`, which doesn't take these into account. + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#fullStackTracePatterns") + void full_output_should_match_Throwable_printStackTrace(final String pattern) { + final String expectedStackTrace = renderStackTraceUsingJava(); + final String effectivePattern = patternPrefix + pattern; + final String actualStackTrace = convert(effectivePattern); + assertThat(actualStackTrace).as("pattern=`%s`", effectivePattern).isEqualTo(expectedStackTrace); + } + + // This test does not provide `separator` and `suffix` options, since the reference output will be obtained from + // `Throwable#printStackTrace()`, which doesn't take these into account. + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#maxLineCounts") + void depth_limited_output_should_match_Throwable_printStackTrace(final int maxLineCount) { + final String expectedStackTrace = renderStackTraceUsingJava(maxLineCount); + final String effectivePattern = patternPrefix + '{' + maxLineCount + '}'; + final String actualStackTrace = convert(effectivePattern); + assertThat(actualStackTrace).as("pattern=`%s`", effectivePattern).isEqualTo(expectedStackTrace); + } + + private String renderStackTraceUsingJava(final int maxLineCount) { + if (maxLineCount == 0) { + return ""; + } + final String stackTrace = renderStackTraceUsingJava(); + if (maxLineCount == Integer.MAX_VALUE) { + return stackTrace; + } + return limitLines(stackTrace, maxLineCount); + } + + private String limitLines(final String text, final int maxLineCount) { + final StringBuilder buffer = new StringBuilder(); + int lineCount = 0; + int startIndex = 0; + int newlineIndex; + while (lineCount < maxLineCount && (newlineIndex = text.indexOf(LINE_SEPARATOR, startIndex)) != -1) { + final int endIndex = newlineIndex + LINE_SEPARATOR.length(); + final String line = text.substring(startIndex, endIndex); + buffer.append(line); + lineCount++; + startIndex = endIndex; + } + return buffer.toString(); + } + + private String renderStackTraceUsingJava() { + final Charset charset = StandardCharsets.UTF_8; + try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final PrintStream printStream = new PrintStream(outputStream, false, charset.name())) { + EXCEPTION.printStackTrace(printStream); + printStream.flush(); + return new String(outputStream.toByteArray(), charset); + } catch (final Exception error) { + throw new RuntimeException(error); + } + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_limited_output_should_match(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.(TestFriendlyException.java:0)", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT, + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]")); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_1(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(foo)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " ... suppressed 2 lines", + " at " + TestFriendlyException.ORG_APACHE_REPLACEMENT_STACK_TRACE_ELEMENT, + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " ... suppressed 2 lines", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " ... suppressed 2 lines", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " ... suppressed 2 lines", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " ... suppressed 2 lines", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]")); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#depthTestCases") + void depth_and_package_limited_output_should_match_2(final DepthTestCase depthTestCase) { + final String pattern = String.format( + "%s{%d}{filters(bar)}%s", + patternPrefix, depthTestCase.maxLineCount, depthTestCase.separatorTestCase.patternAddendum); + assertStackTraceLines( + depthTestCase, + pattern, + asList( + "foo.TestFriendlyException: r [localized]", + " at " + TestFriendlyException.NAMED_MODULE_STACK_TRACE_ELEMENT, + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.(TestFriendlyException.java:0)", + " ...", + " Suppressed: foo.TestFriendlyException: r_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_s_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Caused by: foo.TestFriendlyException: r_s_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Caused by: foo.TestFriendlyException: r_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 2 more", + " Suppressed: foo.TestFriendlyException: r_c_s [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + " Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]", + "Caused by: foo.TestFriendlyException: r_c_c [localized]", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " at foo.TestFriendlyException.create(TestFriendlyException.java:0)", + " ... 3 more", + "Caused by: [CIRCULAR REFERENCE: foo.TestFriendlyException: r_c [localized]]")); + } + } + + abstract static class AbstractStackTraceTest { + + final String patternPrefix; + + AbstractStackTraceTest(final String patternPrefix) { + this.patternPrefix = patternPrefix; + } + + @Test + void output_should_be_newline_prefixed() { + final String pattern = "%p" + patternPrefix; + final String stackTrace = convert(pattern); + final String expectedStart = + String.format("%s%n%s", LEVEL, EXCEPTION.getClass().getCanonicalName()); + assertThat(stackTrace).as("pattern=`%s`", pattern).startsWith(expectedStart); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.pattern.ThrowablePatternConverterTest#separatorTestCases") + void none_output_should_be_empty(final SeparatorTestCase separatorTestCase) { + final String effectivePattern = patternPrefix + "{none}" + separatorTestCase.patternAddendum; + final String stackTrace = convert(effectivePattern); + assertThat(stackTrace).as("pattern=`%s`", effectivePattern).isEmpty(); + } + + void assertStackTraceLines( + @Nullable final DepthTestCase depthTestCase, + final String pattern, + final List expectedStackTraceLines) { + final String actualStackTrace = convert(pattern); + final int maxLineCount; + final String conversionEnding; + if (depthTestCase == null) { + maxLineCount = Integer.MAX_VALUE; + conversionEnding = LINE_SEPARATOR; + } else { + maxLineCount = depthTestCase.maxLineCount; + conversionEnding = depthTestCase.separatorTestCase.conversionEnding; + } + final String expectedStackTrace = expectedStackTraceLines.stream() + .limit(maxLineCount) + .map(expectedStackTraceLine -> expectedStackTraceLine + conversionEnding) + .collect(Collectors.joining()); + final String truncatedActualStackTrace = normalizeStackTrace(actualStackTrace, conversionEnding); + final String truncatedExpectedStackTrace = normalizeStackTrace(expectedStackTrace, conversionEnding); + assertThat(truncatedActualStackTrace) + .as("depthTestCase=%s, pattern=`%s`", depthTestCase, pattern) + .isEqualTo(truncatedExpectedStackTrace); + } + + private static String normalizeStackTrace(final String stackTrace, final String conversionEnding) { + return stackTrace + // Normalize line numbers + .replaceAll("\\.java:[0-9]+\\)", ".java:0)") + // Normalize extended stack trace resource information for Java Standard library classes. + // We replace the `~[?:1.8.0_422]` suffix of such classes with `~[?:0]`. + .replaceAll(" ~\\[\\?:[^]]+](\\Q" + conversionEnding + "\\E|$)", " ~[?:0]$1"); + } + } + + static String convert(final String pattern) { + final List patternFormatters = PATTERN_PARSER.parse(pattern, false, true, true); + final LogEvent logEvent = + Log4jLogEvent.newBuilder().setThrown(EXCEPTION).setLevel(LEVEL).build(); + final StringBuilder buffer = new StringBuilder(); + for (final PatternFormatter patternFormatter : patternFormatters) { + patternFormatter.format(logEvent, buffer); + } + return buffer.toString(); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..261924cbf4c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.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.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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.jupiter.api.Test; + +class VariablesNotEmptyReplacementConverterTest { + + @Test + void testMarkerReplacement() { + testReplacement("%marker", Strings.EMPTY); + } + + @Test + void testMultipleMarkerReplacementEmpty() { + testReplacement("<%marker>Foo", Strings.EMPTY); + } + + @Test + void testMultipleMarkerReplacementSomeEmpty() { + testReplacement("<%marker>%msg", "[<>This is a test]"); + } + + @Test + void testMultipleMarkerReplacement() { + testReplacement("<%level>%msg", "[This is a test]"); + } + + @Test + void testMarkerSimpleNameReplacement() { + testReplacement("%markerSimpleName", Strings.EMPTY); + } + + @Test + void testLoggerNameReplacement() { + testReplacement("%logger", "[" + VariablesNotEmptyReplacementConverterTest.class.getName() + "]"); + } + + @Test + void empty_NDC_should_be_replaced() { + testReplacement("%NDC", ""); + } + + @Test + void empty_MDC_should_be_replaced() { + testReplacement("%mdc", ""); + } + + @Test + void MDC_with_non_existent_key_should_be_replaced() { + testReplacement("foo=%mdc{noSuchKey1}", ""); + } + + @Test + void MDC_with_non_existent_keys_should_be_replaced() { + testReplacement("foo=%mdc{noSuchKey1,noSuchKey2}", ""); + } + + private void testReplacement(final String tag, final String expectedValue) { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(VariablesNotEmptyReplacementConverterTest.class.getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("This is a test")) // + .build(); + final StringBuilder sb = new StringBuilder(); + final LoggerContext ctx = LoggerContext.getContext(); + 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/script/AbstractScriptTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/script/AbstractScriptTest.java new file mode 100644 index 00000000000..d4cbef56064 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/script/AbstractScriptTest.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.script; + +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 org.junit.jupiter.api.Test; + +class AbstractScriptTest { + + @Test + void testConstructorAndGettersWithExplicitName() { + final String lang = "JavaScript"; + final String text = "text"; + final String name = "script"; + final AbstractScript script = new MockScript(name, lang, text); + + assertEquals(lang, script.getLanguage(), "Language should match"); + assertEquals(text, script.getScriptText(), "Script text should match"); + assertEquals(name, script.getName(), "Name should match the provided name"); + assertEquals(name, script.getId(), "Id should match the provided name"); + } + + @Test + void testConstructorAndGettersWithImplicitName() { + final String lang = "JavaScript"; + final String text = "text"; + final AbstractScript script = new MockScript(null, lang, text); + + assertEquals(lang, script.getLanguage(), "Language should match"); + assertEquals(text, script.getScriptText(), "Script text should match"); + assertNull(script.getName(), "Name should be null"); + assertNotNull(script.getId(), "Id should not be null"); + } + + @Test + void testConstructorAndGettersWithBlankName() { + final String lang = "JavaScript"; + final String text = "text"; + final String name = " "; + final AbstractScript script = new MockScript(name, lang, text); + + assertEquals(lang, script.getLanguage(), "Language should match"); + assertEquals(text, script.getScriptText(), "Script text should match"); + assertEquals(name, script.getName(), "Name should be blank"); + assertNotNull(script.getId(), "Id should not be null"); + } + + private class MockScript extends AbstractScript { + + public MockScript(String name, String language, String scriptText) { + super(name, language, scriptText); + } + } +} 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..5c3b50bbf44 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.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.selector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +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.util.Constants; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class BasicContextSelectorTest { + + @BeforeAll + static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BasicContextSelector.class.getName()); + } + + @AfterAll + static void afterClass() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + } + + @Test + void testLogManagerShutdown() { + final LoggerContext context = (LoggerContext) LogManager.getContext(); + assertEquals(LifeCycle.State.STARTED, context.getState()); + LogManager.shutdown(); + assertEquals(LifeCycle.State.STOPPED, context.getState()); + } + + @Test + void testNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..fd6220f7e14 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.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.selector; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import java.lang.reflect.Field; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.util.ReflectionUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ClassLoaderContextSelectorTest { + + private static final String PKG = + ClassLoaderContextSelectorTest.class.getPackage().getName(); + + private ClassLoader loader1, loader2, loader3; + + @BeforeEach + void setUp() { + loader1 = new TestClassLoader(); + loader2 = new TestClassLoader(); + loader3 = new TestClassLoader(); + assertNotSame(loader1, loader2); + assertNotSame(loader1, loader3); + assertNotSame(loader2, loader3); + } + + @Test + void testMultipleClassLoaders() throws Exception { + final Class logging1 = loader1.loadClass(PKG + ".a.Logging1"); + final Field field1 = logging1.getDeclaredField("logger"); + final Logger logger1 = (Logger) ReflectionUtil.getStaticFieldValue(field1); + assertNotNull(logger1); + final Class logging2 = loader2.loadClass(PKG + ".b.Logging2"); + final Field field2 = logging2.getDeclaredField("logger"); + final Logger logger2 = (Logger) ReflectionUtil.getStaticFieldValue(field2); + assertNotNull(logger2); + final Class logging3 = loader3.loadClass(PKG + ".c.Logging3"); + final Field field3 = logging3.getDeclaredField("logger"); + final Logger logger3 = (Logger) ReflectionUtil.getStaticFieldValue(field3); + assertNotNull(logger3); + assertNotSame(logger1.getContext(), logger2.getContext()); + assertNotSame(logger1.getContext(), logger3.getContext()); + assertNotSame(logger2.getContext(), logger3.getContext()); + } +} 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 76% 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..94860d51f5c 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.selector; @@ -21,7 +21,6 @@ import java.io.InputStream; import java.net.URL; import java.net.URLConnection; - import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.core.util.Closer; import org.apache.logging.log4j.core.util.Throwables; @@ -54,9 +53,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-test/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 new file mode 100644 index 00000000000..ff111c0be63 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/a/Logging1.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.core.selector.a; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +// loaded dynamically +@SuppressWarnings("UnusedDeclaration") +public class Logging1 { + static Logger logger = LogManager.getLogger(); +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..0a583765408 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/b/Logging2.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.core.selector.b; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +// loaded dynamically +@SuppressWarnings("UnusedDeclaration") +public class Logging2 { + static Logger logger = LogManager.getLogger(); +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..3687c23b6bb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/c/Logging3.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.core.selector.c; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +// loaded dynamically +@SuppressWarnings("UnusedDeclaration") +public class Logging3 { + static Logger logger = LogManager.getLogger(); +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.java new file mode 100644 index 00000000000..c69b9c3994d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/LogBuilderTest.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.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +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.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@LoggerContextSource("org/apache/logging/log4j/core/test/LogBuilderTest.xml") +class LogBuilderTest { + + private static final Marker MARKER = MarkerManager.getMarker("TestMarker"); + private static final CharSequence CHAR_SEQUENCE = "CharSequence"; + private static final String STRING = "String"; + private static final Message MESSAGE = new SimpleMessage(); + private static final Throwable THROWABLE = new RuntimeException(); + private static final Object[] P = {"p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"}; + private static final Object OBJECT = "Object"; + + private Logger logger; + private ListAppender appender; + + @BeforeEach + void setupAppender(final LoggerContext context, @Named("LIST") final ListAppender appender) { + this.logger = context.getLogger(getClass()); + this.appender = appender; + } + + static Stream> testMarkerFilter() { + return Stream.of( + logBuilder -> logBuilder.log(), + logBuilder -> logBuilder.log(CHAR_SEQUENCE), + logBuilder -> logBuilder.log(MESSAGE), + logBuilder -> logBuilder.log(OBJECT), + logBuilder -> logBuilder.log(STRING), + logBuilder -> logBuilder.log(STRING, P[0]), + logBuilder -> logBuilder.log(STRING, P[0], P[1]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]), + logBuilder -> logBuilder.log(STRING, P), + logBuilder -> logBuilder.log(STRING, () -> OBJECT), + logBuilder -> logBuilder.log(() -> MESSAGE)); + } + + @ParameterizedTest + @MethodSource + void testMarkerFilter(final Consumer consumer) { + appender.clear(); + consumer.accept(logger.atTrace().withMarker(MARKER)); + consumer.accept(logger.atTrace().withThrowable(THROWABLE).withMarker(MARKER)); + assertThat(appender.getEvents()).hasSize(2); + } +} 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..966cddca98a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import org.apache.logging.log4j.core.time.internal.FixedPreciseClock; +import org.junit.jupiter.api.Test; + +class MutableInstantTest { + + @Test + void testGetEpochSecond() { + final 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"); + + final 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() { + final 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"); + + final 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() { + final 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"); + + final 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() { + final 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"); + + final 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() { + final MutableInstant other = new MutableInstant(); + other.initFromEpochSecond(788, 456); + assertEquals(788, other.getEpochSecond(), "epochSec"); + assertEquals(456, other.getNanoOfSecond(), "NanosOfSec"); + + final MutableInstant instant = new MutableInstant(); + instant.initFrom(other); + + assertEquals(788, instant.getEpochSecond(), "epochSec"); + assertEquals(456, instant.getNanoOfSecond(), "NanoOfSec"); + } + + @Test + void testInitFromEpochMillis() { + final 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() { + final MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochMilli(123456, -1)); + } + + @Test + void testInitFromEpochMillisRejectsTooLargeNanoOfMilli() { + final MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochMilli(123456, 1000_000)); + } + + @Test + void testInitFromEpochMillisAcceptsTooMaxNanoOfMilli() { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli(123456, 999_999); + assertEquals(999_999, instant.getNanoOfMillisecond(), "NanoOfMilli"); + } + + @Test + void testInitFromEpochSecond() { + final 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() { + final MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochSecond(123456, -1)); + } + + @Test + void testInitFromEpochSecondRejectsTooLargeNanoOfMilli() { + final MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochSecond(123456, 1000_000_000)); + } + + @Test + void testInitFromEpochSecondAcceptsTooMaxNanoOfMilli() { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123456, 999_999_999); + assertEquals(999_999_999, instant.getNanoOfSecond(), "NanoOfSec"); + } + + @Test + void testInstantToMillisAndNanos() { + final 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() { + final MutableInstant instant = new MutableInstant(); + + final 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() { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + + final MutableInstant instant2 = new MutableInstant(); + instant2.initFromEpochMilli(123456, 789012); + + assertEquals(instant, instant2); + } + + @Test + void testHashCode() { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + + final 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() { + final 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() { + final java.time.Instant javaInstant = java.time.Instant.parse("2020-05-10T22:09:04.123456789Z"); + final MutableInstant log4jInstant = new MutableInstant(); + log4jInstant.initFromEpochSecond(javaInstant.getEpochSecond(), javaInstant.getNano()); + final 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-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..dfa631bc933 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.test.TestLogger; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class GenerateCustomLoggerTest { + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j2.loggerContextFactory", "org.apache.logging.log4j.test.TestLoggerContextFactory"); + } + + @Test + @SuppressWarnings("ReturnValueIgnored") + 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(Collections.singletonList(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.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..93349f2c0d9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.test.TestLogger; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("functional") +class GenerateExtendedLoggerTest { + + @BeforeAll + static void beforeClass() { + System.setProperty("log4j2.loggerContextFactory", "org.apache.logging.log4j.test.TestLoggerContextFactory"); + } + + @Test + @SuppressWarnings("ReturnValueIgnored") + 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(Collections.singletonList(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.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/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java index 309cc77a2b4..de71272c072 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ClockFactoryTest.java @@ -1,30 +1,29 @@ /* * 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.logging.log4j.core.util; -import java.lang.reflect.Field; +import static org.junit.jupiter.api.Assertions.assertSame; +import java.lang.reflect.Field; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.logging.log4j.core.async.AsyncLogger; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ClockFactoryTest { @@ -36,53 +35,52 @@ public static void resetClocks() throws IllegalAccessException { public static void resetClock(final Class clazz) throws IllegalAccessException { System.clearProperty(ClockFactory.PROPERTY_NAME); final Field field = FieldUtils.getField(clazz, "CLOCK", true); - FieldUtils.removeFinalModifier(field, true); FieldUtils.writeStaticField(field, ClockFactory.getClock(), false); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { resetClocks(); } @Test - public void testDefaultIsSystemClock() { + void testDefaultIsSystemClock() { System.clearProperty(ClockFactory.PROPERTY_NAME); assertSame(SystemClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifySystemClockShort() { + void testSpecifySystemClockShort() { System.setProperty(ClockFactory.PROPERTY_NAME, "SystemClock"); assertSame(SystemClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifySystemClockLong() { + void testSpecifySystemClockLong() { System.setProperty(ClockFactory.PROPERTY_NAME, SystemClock.class.getName()); assertSame(SystemClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifyCachedClockShort() { + void testSpecifyCachedClockShort() { System.setProperty(ClockFactory.PROPERTY_NAME, "CachedClock"); assertSame(CachedClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifyCachedClockLong() { + void testSpecifyCachedClockLong() { System.setProperty(ClockFactory.PROPERTY_NAME, CachedClock.class.getName()); assertSame(CachedClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifyCoarseCachedClockShort() { + void testSpecifyCoarseCachedClockShort() { System.setProperty(ClockFactory.PROPERTY_NAME, "CoarseCachedClock"); assertSame(CoarseCachedClock.class, ClockFactory.getClock().getClass()); } @Test - public void testSpecifyCoarseCachedClockLong() { + void testSpecifyCoarseCachedClockLong() { System.setProperty(ClockFactory.PROPERTY_NAME, CoarseCachedClock.class.getName()); assertSame(CoarseCachedClock.class, ClockFactory.getClock().getClass()); } @@ -95,9 +93,8 @@ public long currentTimeMillis() { } @Test - public void testCustomClock() { + void testCustomClock() { System.setProperty(ClockFactory.PROPERTY_NAME, MyClock.class.getName()); assertSame(MyClock.class, ClockFactory.getClock().getClass()); } - } 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..3e00b8c5532 --- /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 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.HashMap; +import java.util.List; +import java.util.Map; +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; + +@Tag("functional") +class ContextDataProviderTest { + + private static Logger logger; + private static ListAppender appender; + + @BeforeAll + static void beforeClass() { + ThreadContextDataInjector.contextDataProviders.add(new TestContextDataProvider()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "log4j-contextData.xml"); + final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + logger = loggerContext.getLogger(ContextDataProviderTest.class.getName()); + appender = loggerContext.getConfiguration().getAppender("List"); + assertNotNull(appender, "No List appender"); + } + + @Test + void testContextProvider() { + ThreadContext.put("loginId", "jdoe"); + logger.debug("This is a test"); + final 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() { + final 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..8a93e2b58c0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import org.junit.jupiter.api.Test; + +/** + * Class Description goes here. + * Created by rgoers on 11/15/15 + */ +class CronExpressionTest { + + @Test + void testDayOfMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 * * ?"); + final Date date = new GregorianCalendar(2015, 11, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, 11, 2, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testDayOfWeek() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 ? * Fri"); + final Date date = new GregorianCalendar(2015, 11, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, 11, 4, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testNextMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 1 * ?"); + final Date date = new GregorianCalendar(2015, 11, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2016, 0, 1, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testLastDayOfMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 L * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, 10, 30, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testNextDay() throws Exception { + final CronExpression parser = new CronExpression("0 0 0 * * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, 10, 3, 0, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testPrevFireTime1() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 L * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 9, 31, 17, 45, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + void testPrevFireTime2() throws Exception { + final CronExpression parser = new CronExpression("0 0/5 14,18 * * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 10, 1, 18, 55, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * 35,45, and 55 minutes past the hour every hour. + */ + @Test + void testPrevFireTime3() throws Exception { + final CronExpression parser = new CronExpression("0 35/10 * * * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 10, 1, 23, 55, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * + * 10:15 every day. + */ + @Test + void testPrevFireTimeTenFifteen() throws Exception { + final CronExpression parser = new CronExpression("0 15 10 * * ? *"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 10, 1, 10, 15, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * Every day from 2 pm to 2:59 pm + */ + @Test + void testPrevFireTimeTwoPM() throws Exception { + final CronExpression parser = new CronExpression("0 * 14 * * ?"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 10, 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 + void testPrevFireTimeMarch() throws Exception { + final CronExpression parser = new CronExpression("0 10,44 14 ? 3 WED"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 2, 25, 14, 44, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * Fire at 10:15am on the third Friday of every month. + */ + @Test + void testPrevFireTimeThirdFriday() throws Exception { + final CronExpression parser = new CronExpression("0 15 10 ? * 6#3"); + final Date date = new GregorianCalendar(2015, 10, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, 9, 16, 10, 15, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /* + * Input time with milliseconds will correctly return the next + * scheduled time. + */ + @Test + void testTimeBeforeMilliseconds() throws Exception { + final CronExpression parser = new CronExpression("0 0 0 * * ?"); + final GregorianCalendar cal = new GregorianCalendar(2015, 10, 2, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 100); + final Date date = cal.getTime(); + final 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, 10, 1, 0, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..e1428b33ce2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.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 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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class CyclicBufferTest { + + @Test + void testSize0() { + final CyclicBuffer buffer = new CyclicBuffer<>(Integer.class, 0); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + assertTrue(buffer.isEmpty()); + Integer[] items = buffer.removeAll(); + assertEquals(0, items.length, "Incorrect number of items"); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + buffer.add(2); + buffer.add(3); + buffer.add(4); + items = buffer.removeAll(); + assertEquals(0, items.length, "Incorrect number of items"); + assertTrue(buffer.isEmpty()); + } + + @Test + void testSize1() { + final CyclicBuffer buffer = new CyclicBuffer<>(Integer.class, 1); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + assertFalse(buffer.isEmpty()); + Integer[] items = buffer.removeAll(); + assertEquals(1, items.length, "Incorrect number of items"); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + buffer.add(2); + buffer.add(3); + buffer.add(4); + items = buffer.removeAll(); + assertEquals(1, items.length, "Incorrect number of items"); + assertArrayEquals(new Integer[] {4}, items); + assertTrue(buffer.isEmpty()); + } + + @Test + void testSize3() { + final CyclicBuffer buffer = new CyclicBuffer<>(Integer.class, 3); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + assertFalse(buffer.isEmpty()); + Integer[] items = buffer.removeAll(); + assertEquals(1, items.length, "Incorrect number of items"); + + assertTrue(buffer.isEmpty()); + buffer.add(1); + buffer.add(2); + buffer.add(3); + buffer.add(4); + items = buffer.removeAll(); + assertEquals(3, items.length, "Incorrect number of items"); + assertArrayEquals(new Integer[] {2, 3, 4}, items); + assertTrue(buffer.isEmpty()); + } + + @Test + void testSizeNegative() { + assertThrows(IllegalArgumentException.class, () -> new CyclicBuffer<>(Integer.class, -1)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.java new file mode 100644 index 00000000000..4e4559a52c0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Tests the DummyNanoClock. + */ +class DummyNanoClockTest { + + @Test + void testReturnsZeroByDefault() { + assertEquals(0, new DummyNanoClock().nanoTime()); + } + + @Test + void testReturnsConstructorValue() { + assertEquals(123, new DummyNanoClock(123).nanoTime()); + } +} 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..35ec485703c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Tests the FileUtils class. + */ +class FileUtilsTest { + + private static final String LOG4J_CONFIG_WITH_PLUS = "log4j+config+with+plus+characters.xml"; + + @Test + 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 + void testAbsoluteFileFromUriWithPlusCharactersInName() { + 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 + void testAbsoluteFileFromUriWithSpacesInName() { + 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 + void testAbsoluteFileFromJBossVFSUri() { + 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 + 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"); + } + + @Nested + class TestMkdir { + @TempDir + File testDir; + + @BeforeEach + void deleteTestDir() throws IOException { + org.apache.commons.io.FileUtils.deleteDirectory(testDir); + } + + @Test + void testMkdirDoesntExistDontCreate() { + assertThrows(IOException.class, () -> FileUtils.mkdir(testDir, false)); + } + + @Test + void testMkdirFileAlreadyExistsNotDir() throws IOException { + Files.createFile(testDir.toPath()); + assertThrows(IOException.class, () -> FileUtils.mkdir(testDir, true)); + Files.delete(testDir.toPath()); + } + + @Test + void testMkdirConcurrent() throws InterruptedException { + final List threads = new ArrayList<>(); + final AtomicBoolean anyThreadThrows = new AtomicBoolean(false); + for (int i = 0; i < 10000; i++) { + threads.add(new Thread(() -> { + try { + FileUtils.mkdir(testDir, true); + } catch (IOException e) { + anyThreadThrows.set(true); + } + })); + } + + for (Thread t : threads) { + t.start(); + } + for (Thread t : threads) { + t.join(); + } + + assertFalse(anyThreadThrows.get()); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java new file mode 100644 index 00000000000..774f36d5aa6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.net.URL; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +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.HttpWatcher; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.apache.logging.log4j.test.junit.SetTestProperty; +import org.junit.jupiter.api.Test; + +/** + * Test the WatchManager + */ +@SetTestProperty(key = UrlConnectionFactory.ALLOWED_PROTOCOLS, value = "http,https") +@WireMockTest +class HttpWatcherTest { + + private static final DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); + private static final String XML = "application/xml"; + + @Test + void testModified(final WireMockRuntimeInfo info) throws Exception { + final WireMock wireMock = info.getWireMock(); + + final BlockingQueue queue = new LinkedBlockingQueue<>(); + List listeners = singletonList(new TestConfigurationListener(queue, "log4j-test1.xml")); + // HTTP Last-Modified is in seconds + Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS); + Instant previous = now.minus(5, ChronoUnit.MINUTES); + final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test1.xml"); + final Configuration configuration = createConfiguration(url); + + final StubMapping stubMapping = wireMock.register(get("/log4j-test1.xml") + .willReturn(aResponse() + .withBodyFile("log4j-test1.xml") + .withStatus(200) + .withHeader("Last-Modified", formatter.format(previous)) + .withHeader("Content-Type", XML))); + Watcher watcher = new HttpWatcher(configuration, null, listeners, previous.toEpochMilli()); + watcher.watching(new Source(url)); + try { + assertThat(watcher.isModified()).as("File was modified").isTrue(); + assertThat(watcher.getLastModified()).as("File modification time").isEqualTo(previous.toEpochMilli()); + // Check if listeners are correctly called + // Note: listeners are called asynchronously + watcher.modified(); + String str = queue.poll(1, TimeUnit.SECONDS); + assertThat(str).isEqualTo("log4j-test1.xml"); + ConfigurationSource configurationSource = configuration.getConfigurationSource(); + // Check that the last modified time of the ConfigurationSource was modified as well + // See: https://github.com/apache/logging-log4j2/issues/2937 + assertThat(configurationSource.getLastModified()) + .as("Last modification time of current ConfigurationSource") + .isEqualTo(0L); + configurationSource = configurationSource.resetInputStream(); + assertThat(configurationSource.getLastModified()) + .as("Last modification time of next ConfigurationSource") + .isEqualTo(previous.toEpochMilli()); + } finally { + wireMock.removeStubMapping(stubMapping); + } + } + + @Test + void testNotModified(final WireMockRuntimeInfo info) throws Exception { + final WireMock wireMock = info.getWireMock(); + + final BlockingQueue queue = new LinkedBlockingQueue<>(); + final List listeners = + singletonList(new TestConfigurationListener(queue, "log4j-test2.xml")); + // HTTP Last-Modified is in seconds + Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS); + Instant previous = now.minus(5, ChronoUnit.MINUTES); + final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test2.xml"); + final Configuration configuration = createConfiguration(url); + + final StubMapping stubMapping = wireMock.register(get("/log4j-test2.xml") + .willReturn(aResponse() + .withStatus(304) + .withHeader("Last-Modified", formatter.format(now) + " GMT") + .withHeader("Content-Type", XML))); + Watcher watcher = new HttpWatcher(configuration, null, listeners, previous.toEpochMilli()); + watcher.watching(new Source(url)); + try { + assertThat(watcher.isModified()).as("File was modified").isFalse(); + // If the file was not modified, neither should be the last modification time + assertThat(watcher.getLastModified()).isEqualTo(previous.toEpochMilli()); + // Check that the last modified time of the ConfigurationSource was not modified either + ConfigurationSource configurationSource = configuration.getConfigurationSource(); + assertThat(configurationSource.getLastModified()) + .as("Last modification time of current ConfigurationSource") + .isEqualTo(0L); + configurationSource = configurationSource.resetInputStream(); + assertThat(configurationSource.getLastModified()) + .as("Last modification time of next ConfigurationSource") + .isEqualTo(0L); + } finally { + wireMock.removeStubMapping(stubMapping); + } + } + + // Creates a configuration with a predefined configuration source + private static Configuration createConfiguration(URL url) { + ConfigurationSource configurationSource = new ConfigurationSource(new Source(url), new byte[0], 0L); + return new AbstractConfiguration(null, configurationSource) {}; + } + + private static class TestConfigurationListener implements ConfigurationListener { + private final Queue queue; + private final String name; + + public TestConfigurationListener(final Queue queue, final String name) { + this.queue = queue; + this.name = name; + } + + @Override + public void onChange(final Reconfigurable reconfigurable) { + // System.out.println("Reconfiguration detected for " + name); + queue.add(name); + } + } +} 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..f239e7a2eed --- /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 static org.junit.jupiter.api.Assertions.assertTrue; + +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; + +/** + * Test initialization. + */ +@Disabled +class InitTest { + + @Test + void initTest() { + final Timer timer = new Timer("Log4j Initialization"); + timer.start(); + final Logger logger = LogManager.getLogger(); + timer.stop(); + final long elapsed = timer.getElapsedNanoTime(); + System.out.println(timer); + assertTrue(elapsed < 1000000000, "Initialization time exceeded threshold; elapsed " + elapsed); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..ae736d79cbc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Tests the Integers class. + */ +class IntegersTest { + + @Test + void testCeilingNextPowerOfTwoReturnsNextPowerOfTwo() { + final int powerOfTwo = Integers.ceilingNextPowerOfTwo(1000); + assertEquals(1024, powerOfTwo); + } + + @Test + void testCeilingNextPowerOfTwoReturnsExactPowerOfTwo() { + final int powerOfTwo = Integers.ceilingNextPowerOfTwo(1024); + assertEquals(1024, powerOfTwo); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..4d64bdac9aa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * This class is borrowed from Jackson. + */ +class JsonUtilsTest { + + @Test + void testQuoteCharSequenceAsString() { + final StringBuilder output = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); + builder.append("foobar"); + JsonUtils.quoteAsString(builder, output); + assertEquals("foobar", output.toString()); + builder.setLength(0); + output.setLength(0); + builder.append("\"x\""); + JsonUtils.quoteAsString(builder, output); + assertEquals("\\\"x\\\"", output.toString()); + } + + // For [JACKSON-853] + @Test + void testQuoteLongCharSequenceAsString() { + final StringBuilder output = new StringBuilder(); + final StringBuilder input = new StringBuilder(); + final StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < 1111; ++i) { + input.append('"'); + sb2.append("\\\""); + } + final String exp = sb2.toString(); + JsonUtils.quoteAsString(input, output); + assertEquals(2 * input.length(), output.length()); + assertEquals(exp, output.toString()); + } + + // [JACKSON-884] + @Test + void testCharSequenceWithCtrlChars() { + final char[] input = new char[] {0, 1, 2, 3, 4}; + final StringBuilder builder = new StringBuilder(); + builder.append(input); + final StringBuilder output = new StringBuilder(); + JsonUtils.quoteAsString(builder, output); + assertEquals("\\u0000\\u0001\\u0002\\u0003\\u0004", output.toString()); + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..06b85795516 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** + * Tests the Loader class. + */ +class LoaderTest { + + @Test + void testLoadClassWithNullClassloaderReturnNull() throws Exception { + assertNull( + Loader.loadClass(Loader.class.getCanonicalName(), null), + "Expect null return value for null ClassLoader."); + } + + @Test + void testLoadClassReturnClassForExistingClass() throws Exception { + assertEquals( + Loader.class, + Loader.loadClass(Loader.class.getCanonicalName(), Loader.getClassLoader()), + "Expect Class return value for null ClassLoader."); + } +} 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..9afa5f2069a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.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.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +class NetUtilsTest { + + @Test + 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 + 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) + 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) + 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."); + } + + @Test + void testCanonicalHostName() throws UnknownHostException { + assumeThat(InetAddress.getLocalHost().getCanonicalHostName()).contains("."); + // If this fails the host might be misconfigured + assertThat(NetUtils.getCanonicalLocalHostname()).contains("."); + } +} 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..f4234e43adc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/OptionConverterTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Properties; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link OptionConverter}. + */ +class OptionConverterTest { + + @Test + void testSubstVars() { + final 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 + void testAppend() { + final 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 + void testAppend2() { + final 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 + void testRecursion() { + final Properties props = new RecursiveProperties(); + props.setProperty("name", "Neo"); + props.setProperty("greeting", "Hello ${name}"); + + final String s = props.getProperty("greeting"); + System.out.println("greeting = '" + s + "'"); + } + + private static class RecursiveProperties extends Properties { + @Override + public String getProperty(final String key) { + System.out.println("getProperty for " + key); + try { + final 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/ShutdownCallbackRegistryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java new file mode 100644 index 00000000000..55f52d79bca --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; +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.status.StatusLogger; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class ShutdownCallbackRegistryTest { + + @BeforeAll + static void setUpClass() { + System.setProperty("log4j2.shutdownHookEnabled", "true"); + System.setProperty(ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, Registry.class.getName()); + } + + @AfterAll + static void afterClass() { + System.clearProperty(ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY); + System.clearProperty("log4j2.shutdownHookEnabled"); + } + + @Test + @LoggerContextSource("ShutdownCallbackRegistryTest.xml") + 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))); + } + + 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..4d2db85972c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SourceTest.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +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}. + */ +class SourceTest { + + @Test + 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 + 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.") + void testEqualityURIFile() { + assertEquals( + new Source(Paths.get("foo").toUri()), + new Source(Paths.get("./foo").toUri())); + } + + @Test + 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/./././."))); + } + + @Test + @Disabled + void testEqualityURLFile() throws MalformedURLException { + assertEquals( + new Source(Paths.get("foo").toUri().toURL()), + new Source(Paths.get("./foo").toUri().toURL())); + } + + @Test + @Disabled + 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())); + } + + @Test + @Disabled + 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 + 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 + 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()); + } + + @Test + 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 + void testURIConstructor() { + 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 + void testURIFileConstructor() { + 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 + void testURIHttpConstructor() { + final URI uri = URI.create("http://www.apache.org"); + final Source source = new Source(uri); + assertNull(source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + } + + @Test + void testURIHttpsConstructor() { + final URI uri = URI.create("https://www.apache.org"); + final Source source = new Source(uri); + assertNull(source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + } + + @Test + 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/SystemClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SystemClockTest.java new file mode 100644 index 00000000000..c1441f9e473 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SystemClockTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class SystemClockTest { + + @Test + void testLessThan2Millis() { + final long millis1 = new SystemClock().currentTimeMillis(); + final long sysMillis = System.currentTimeMillis(); + + final long diff = sysMillis - millis1; + + assertTrue(diff <= 1, "diff too large: " + diff); + } + + @Test + void testAfterWaitStillLessThan2Millis() throws Exception { + Thread.sleep(100); + final long millis1 = new SystemClock().currentTimeMillis(); + final long sysMillis = System.currentTimeMillis(); + + final long diff = sysMillis - millis1; + + assertTrue(diff <= 1, "diff too large: " + diff); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.java new file mode 100644 index 00000000000..3e4d4427540 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +/** + * Tests the SystemNanoClock. + */ +class SystemNanoClockTest { + + @Test + 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-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..912d62a1d90 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class ThrowablesTest { + + @Test + void testGetRootCauseNone() { + final NullPointerException throwable = new NullPointerException(); + assertEquals(throwable, Throwables.getRootCause(throwable)); + } + + @Test + void testGetRootCauseDepth1() { + final Throwable cause = new NullPointerException(); + final Throwable error = new UnsupportedOperationException(cause); + assertEquals(cause, Throwables.getRootCause(error)); + } + + @Test + void testGetRootCauseDepth2() { + final Throwable rootCause = new NullPointerException(); + final Throwable cause = new UnsupportedOperationException(rootCause); + final Throwable error = new IllegalArgumentException(cause); + assertEquals(rootCause, Throwables.getRootCause(error)); + } + + @Test + void testGetRootCauseLoop() { + final Throwable cause1 = new RuntimeException(); + final Throwable cause2 = new RuntimeException(cause1); + final Throwable cause3 = new RuntimeException(cause2); + cause1.initCause(cause3); + assertEquals(cause1, Throwables.getRootCause(cause3)); + } + + @Test + void testRethrowRuntimeException() { + assertThrows(NullPointerException.class, () -> Throwables.rethrow(new NullPointerException())); + } + + @Test + void testRethrowError() { + assertThrows(UnknownError.class, () -> Throwables.rethrow(new UnknownError())); + } + + @Test + 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 77% 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 index e5b4eab260d..933b4f6348d 100644 --- 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 @@ -2,7 +2,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 * @@ -11,20 +11,19 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.logging.log4j.core.util; public class UnexpectedFormatException extends Exception { - /** + /** * Generated serial version ID. */ private static final long serialVersionUID = -5322323184538975579L; -public UnexpectedFormatException(final String msg) { - super(msg); - } + public UnexpectedFormatException(final String msg) { + super(msg); + } } diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..be7c214779b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UuidTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class UuidTest { + + private static final int COUNT = 200; + private static final int THREADS = 10; + + private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L; + + @Test + void testTimeBaseUuid() { + final UUID uuid = UuidUtil.getTimeBasedUuid(); + // final UUID uuid2 = UuidUtil.getTimeBasedUUID(); // unused + final long current = (System.currentTimeMillis() * 10000) + NUM_100NS_INTERVALS_SINCE_UUID_EPOCH; + final long time = uuid.timestamp(); + 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) { + uuids[i] = UuidUtil.getTimeBasedUuid(); + } + final long elapsed = System.nanoTime() - start; + System.out.println("Elapsed for " + COUNT + " UUIDS = " + elapsed + " Average = " + elapsed / COUNT + " ns"); + int errors = 0; + for (int i = 0; i < COUNT; ++i) { + for (int j = i + 1; j < COUNT; ++j) { + if (uuids[i].equals(uuids[j])) { + ++errors; + System.out.println("UUID " + i + " equals UUID " + j); + } + } + } + assertEquals(0, errors, errors + " duplicate UUIDS"); + final int variant = uuid.variant(); + assertEquals(2, variant, "Incorrect variant. Expected 2 got " + variant); + final int version = uuid.version(); + assertEquals(1, version, "Incorrect version. Expected 1 got " + version); + final long node = uuid.node(); + assertTrue(node != 0, "Invalid node"); + } + + @Test + 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 + final byte[] mac = new byte[i]; + for (int j = 0; j < i; j++) { + mac[j] = (byte) j; + } + UuidUtil.initialize(mac); + } + } + + @Test + void testThreads() throws Exception { + final Thread[] threads = new Thread[THREADS]; + final UUID[] uuids = new UUID[COUNT * THREADS]; + final long[] elapsed = new long[THREADS]; + for (int i = 0; i < THREADS; ++i) { + threads[i] = new Worker(uuids, elapsed, i, COUNT); + } + for (int i = 0; i < THREADS; ++i) { + threads[i].start(); + } + long elapsedTime = 0; + for (int i = 0; i < THREADS; ++i) { + threads[i].join(); + elapsedTime += elapsed[i]; + } + System.out.println("Elapsed for " + COUNT * THREADS + " UUIDS = " + elapsedTime + " Average = " + + elapsedTime / (COUNT * THREADS) + " ns"); + int errors = 0; + for (int i = 0; i < COUNT * THREADS; ++i) { + for (int j = i + 1; j < COUNT * THREADS; ++j) { + if (uuids[i].equals(uuids[j])) { + ++errors; + System.out.println("UUID " + i + " equals UUID " + j); + } + } + } + assertEquals(0, errors, errors + " duplicate UUIDS"); + } + + private static class Worker extends Thread { + + private final UUID[] uuids; + private final long[] elapsed; + private final int index; + private final int count; + + public Worker(final UUID[] uuids, final long[] elapsed, final int index, final int count) { + this.uuids = uuids; + this.index = index; + this.count = count; + this.elapsed = elapsed; + } + + @Override + public void run() { + final int pos = index * count; + final long start = System.nanoTime(); + for (int i = pos; i < pos + count; ++i) { + uuids[i] = UuidUtil.getTimeBasedUuid(); + } + elapsed[index] = System.nanoTime() - start; + } + } +} diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..49269d7134f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.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.util; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.config.ConfigurationScheduler; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +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; + +/** + * Test the WatchManager + */ +@DisabledOnOs(OS.WINDOWS) +@EnabledIfSystemProperty(named = "WatchManagerTest.forceRun", matches = "true") +class WatchManagerTest { + + 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 ConfigurationScheduler scheduler; + private WatchManager watchManager; + + @BeforeEach + void setUp() { + scheduler = new ConfigurationScheduler(); + scheduler.incrementScheduledItems(); + watchManager = new WatchManager(scheduler); + watchManager.setIntervalSeconds(1); + scheduler.start(); + watchManager.start(); + } + + @AfterEach + void tearDown() { + watchManager.stop(); + scheduler.stop(); + watchManager = null; + scheduler = null; + } + + @Test + void testWatchManager() throws Exception { + final File sourceFile = new File(originalFile); + Path source = Paths.get(sourceFile.toURI()); + try (final FileOutputStream targetStream = new FileOutputStream(testFile)) { + Files.copy(source, targetStream); + } + final File updateFile = new File(newFile); + final File targetFile = new File(testFile); + final BlockingQueue queue = new LinkedBlockingQueue<>(); + watchManager.watchFile(targetFile, new TestWatcher(queue)); + Thread.sleep(1000); + source = Paths.get(updateFile.toURI()); + Files.copy(source, Paths.get(targetFile.toURI()), StandardCopyOption.REPLACE_EXISTING); + Thread.sleep(1000); + final File f = queue.poll(1, TimeUnit.SECONDS); + assertNotNull(f, "File change not detected"); + } + + @Test + void testWatchManagerReset() throws Exception { + final File sourceFile = new File(originalFile); + Path source = Paths.get(sourceFile.toURI()); + try (final FileOutputStream targetStream = new FileOutputStream(testFile)) { + Files.copy(source, targetStream); + } + final File updateFile = new File(newFile); + final File targetFile = new File(testFile); + final BlockingQueue queue = new LinkedBlockingQueue<>(); + watchManager.watchFile(targetFile, new TestWatcher(queue)); + watchManager.stop(); + Thread.sleep(1000); + source = Paths.get(updateFile.toURI()); + Files.copy(source, Paths.get(targetFile.toURI()), StandardCopyOption.REPLACE_EXISTING); + watchManager.reset(); + watchManager.start(); + Thread.sleep(1000); + final File f = queue.poll(1, TimeUnit.SECONDS); + assertNull(f, "File change detected"); + } + + @Test + void testWatchManagerResetFile() throws Exception { + final File sourceFile = new File(originalFile); + Path source = Paths.get(sourceFile.toURI()); + try (final FileOutputStream targetStream = new FileOutputStream(testFile)) { + Files.copy(source, targetStream); + } + final File updateFile = new File(newFile); + final File targetFile = new File(testFile); + final BlockingQueue queue = new LinkedBlockingQueue<>(); + watchManager.watchFile(targetFile, new TestWatcher(queue)); + watchManager.stop(); + Thread.sleep(1000); + source = Paths.get(updateFile.toURI()); + Files.copy(source, Paths.get(targetFile.toURI()), StandardCopyOption.REPLACE_EXISTING); + watchManager.reset(targetFile); + watchManager.start(); + Thread.sleep(1000); + final File f = queue.poll(1, TimeUnit.SECONDS); + assertNull(f, "File change detected"); + } + + /** + * Verify the + */ + @Test + void testWatchManagerCallsWatcher() { + Watcher watcher = mock(Watcher.class); + when(watcher.isModified()).thenReturn(false); + watchManager.watch(new Source(ConfigurationSource.NULL_SOURCE), watcher); + verify(watcher, timeout(2000)).isModified(); + verify(watcher, never()).modified(); + when(watcher.isModified()).thenReturn(true); + clearInvocations(watcher); + verify(watcher, timeout(2000)).isModified(); + verify(watcher).modified(); + } + + private static class TestWatcher implements FileWatcher { + + private final Queue queue; + + public TestWatcher(final Queue queue) { + this.queue = queue; + } + + @Override + public void fileModified(final File file) { + System.out.println(file.toString() + " was modified"); + queue.add(file); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java new file mode 100644 index 00000000000..5a30ab65420 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java @@ -0,0 +1,437 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.datetime; + +import static org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat.DEFAULT; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.util.Strings; +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junitpioneer.jupiter.DefaultLocale; + +/** + * Tests {@link FixedDateFormat}. + */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +class FixedDateFormatTest { + + private boolean containsNanos(final FixedFormat fixedFormat) { + final String pattern = fixedFormat.getPattern(); + return pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*"); + } + + @Test + void testConstructorDisallowsNullFormat() { + assertThrows(NullPointerException.class, () -> new FixedDateFormat(null, TimeZone.getDefault())); + } + + @Test + void testConstructorDisallowsNullTimeZone() { + assertThrows(NullPointerException.class, () -> new FixedDateFormat(FixedFormat.ABSOLUTE, null)); + } + + @Test + void testCreateIfSupported_customTimeZoneIfOptionsArrayWithTimeZoneElement() { + final FixedDateFormat fmt = FixedDateFormat.createIfSupported(DEFAULT.getPattern(), "GMT+08:00", ""); + assertEquals(DEFAULT.getPattern(), fmt.getFormat()); + assertEquals(TimeZone.getTimeZone("GMT+08:00"), fmt.getTimeZone()); + } + + @Test + void testCreateIfSupported_defaultIfOptionsArrayEmpty() { + final FixedDateFormat fmt = FixedDateFormat.createIfSupported(Strings.EMPTY_ARRAY); + assertEquals(DEFAULT.getPattern(), fmt.getFormat()); + } + + @Test + void testCreateIfSupported_defaultIfOptionsArrayNull() { + final FixedDateFormat fmt = FixedDateFormat.createIfSupported((String[]) null); + assertEquals(DEFAULT.getPattern(), fmt.getFormat()); + } + + @Test + void testCreateIfSupported_defaultIfOptionsArrayWithSingleNullElement() { + final FixedDateFormat fmt = FixedDateFormat.createIfSupported(new String[1]); + assertEquals(DEFAULT.getPattern(), fmt.getFormat()); + assertEquals(TimeZone.getDefault(), fmt.getTimeZone()); + } + + @Test + void testCreateIfSupported_defaultTimeZoneIfOptionsArrayWithSecondNullElement() { + final FixedDateFormat fmt = FixedDateFormat.createIfSupported(DEFAULT.getPattern(), null, ""); + assertEquals(DEFAULT.getPattern(), fmt.getFormat()); + assertEquals(TimeZone.getDefault(), fmt.getTimeZone()); + } + + @Test + void testCreateIfSupported_nonNullIfNameMatches() { + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + final String[] options = {format.name()}; + assertNotNull(FixedDateFormat.createIfSupported(options), format.name()); + } + } + + @Test + void testCreateIfSupported_nonNullIfPatternMatches() { + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + final String[] options = {format.getPattern()}; + assertNotNull(FixedDateFormat.createIfSupported(options), format.name()); + } + } + + @Test + void testCreateIfSupported_nullIfNameDoesNotMatch() { + final String[] options = {"DEFAULT3"}; + assertNull(FixedDateFormat.createIfSupported(options), "DEFAULT3"); + } + + @Test + void testCreateIfSupported_nullIfPatternDoesNotMatch() { + final String[] options = {"y M d H m s"}; + assertNull(FixedDateFormat.createIfSupported(options), "y M d H m s"); + } + + @Test + void testDaylightSavingToSummerTime() throws Exception { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse("2017-03-12 00:00:00 UTC")); + + final SimpleDateFormat usCentral = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", Locale.US); + usCentral.setTimeZone(TimeZone.getTimeZone("US/Central")); + + final SimpleDateFormat utc = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", Locale.US); + utc.setTimeZone(TimeZone.getTimeZone("UTC")); + + final FixedDateFormat fixedUsCentral = FixedDateFormat.create(DEFAULT, TimeZone.getTimeZone("US/Central")); + final FixedDateFormat fixedUtc = FixedDateFormat.create(DEFAULT, TimeZone.getTimeZone("UTC")); + + final String[][] expectedDstAndNoDst = { + // US/Central, UTC + {"2017-03-11 18:00:00,000", "2017-03-12 00:00:00,000"}, // + {"2017-03-11 19:00:00,000", "2017-03-12 01:00:00,000"}, // + {"2017-03-11 20:00:00,000", "2017-03-12 02:00:00,000"}, // + {"2017-03-11 21:00:00,000", "2017-03-12 03:00:00,000"}, // + {"2017-03-11 22:00:00,000", "2017-03-12 04:00:00,000"}, // + {"2017-03-11 23:00:00,000", "2017-03-12 05:00:00,000"}, // + {"2017-03-12 00:00:00,000", "2017-03-12 06:00:00,000"}, // + {"2017-03-12 01:00:00,000", "2017-03-12 07:00:00,000"}, // + {"2017-03-12 03:00:00,000", "2017-03-12 08:00:00,000"}, // DST jump at 2am US central time + {"2017-03-12 04:00:00,000", "2017-03-12 09:00:00,000"}, // + {"2017-03-12 05:00:00,000", "2017-03-12 10:00:00,000"}, // + {"2017-03-12 06:00:00,000", "2017-03-12 11:00:00,000"}, // + {"2017-03-12 07:00:00,000", "2017-03-12 12:00:00,000"}, // + {"2017-03-12 08:00:00,000", "2017-03-12 13:00:00,000"}, // + {"2017-03-12 09:00:00,000", "2017-03-12 14:00:00,000"}, // + {"2017-03-12 10:00:00,000", "2017-03-12 15:00:00,000"}, // + {"2017-03-12 11:00:00,000", "2017-03-12 16:00:00,000"}, // + {"2017-03-12 12:00:00,000", "2017-03-12 17:00:00,000"}, // + {"2017-03-12 13:00:00,000", "2017-03-12 18:00:00,000"}, // + {"2017-03-12 14:00:00,000", "2017-03-12 19:00:00,000"}, // + {"2017-03-12 15:00:00,000", "2017-03-12 20:00:00,000"}, // + {"2017-03-12 16:00:00,000", "2017-03-12 21:00:00,000"}, // + {"2017-03-12 17:00:00,000", "2017-03-12 22:00:00,000"}, // + {"2017-03-12 18:00:00,000", "2017-03-12 23:00:00,000"}, // 24 + {"2017-03-12 19:00:00,000", "2017-03-13 00:00:00,000"}, // + {"2017-03-12 20:00:00,000", "2017-03-13 01:00:00,000"}, // + {"2017-03-12 21:00:00,000", "2017-03-13 02:00:00,000"}, // + {"2017-03-12 22:00:00,000", "2017-03-13 03:00:00,000"}, // + {"2017-03-12 23:00:00,000", "2017-03-13 04:00:00,000"}, // + {"2017-03-13 00:00:00,000", "2017-03-13 05:00:00,000"}, // + {"2017-03-13 01:00:00,000", "2017-03-13 06:00:00,000"}, // + {"2017-03-13 02:00:00,000", "2017-03-13 07:00:00,000"}, // + {"2017-03-13 03:00:00,000", "2017-03-13 08:00:00,000"}, // + {"2017-03-13 04:00:00,000", "2017-03-13 09:00:00,000"}, // + {"2017-03-13 05:00:00,000", "2017-03-13 10:00:00,000"}, // + {"2017-03-13 06:00:00,000", "2017-03-13 11:00:00,000"}, // + }; + + for (int i = 0; i < 36; i++) { + final Date date = calendar.getTime(); + assertEquals(expectedDstAndNoDst[i][0], usCentral.format(date), "SimpleDateFormat TZ=US Central"); + assertEquals(expectedDstAndNoDst[i][1], utc.format(date), "SimpleDateFormat TZ=UTC"); + assertEquals( + expectedDstAndNoDst[i][0], fixedUsCentral.format(date.getTime()), "FixedDateFormat TZ=US Central"); + assertEquals(expectedDstAndNoDst[i][1], fixedUtc.format(date.getTime()), "FixedDateFormat TZ=UTC"); + calendar.add(Calendar.HOUR_OF_DAY, 1); + } + } + + @Test + void testDaylightSavingToWinterTime() throws Exception { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse("2017-11-05 00:00:00 UTC")); + + final SimpleDateFormat usCentral = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", Locale.US); + usCentral.setTimeZone(TimeZone.getTimeZone("US/Central")); + + final SimpleDateFormat utc = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", Locale.US); + utc.setTimeZone(TimeZone.getTimeZone("UTC")); + + final FixedDateFormat fixedUsCentral = FixedDateFormat.create(DEFAULT, TimeZone.getTimeZone("US/Central")); + final FixedDateFormat fixedUtc = FixedDateFormat.create(DEFAULT, TimeZone.getTimeZone("UTC")); + + final String[][] expectedDstAndNoDst = { + // US/Central, UTC + {"2017-11-04 19:00:00,000", "2017-11-05 00:00:00,000"}, // + {"2017-11-04 20:00:00,000", "2017-11-05 01:00:00,000"}, // + {"2017-11-04 21:00:00,000", "2017-11-05 02:00:00,000"}, // + {"2017-11-04 22:00:00,000", "2017-11-05 03:00:00,000"}, // + {"2017-11-04 23:00:00,000", "2017-11-05 04:00:00,000"}, // + {"2017-11-05 00:00:00,000", "2017-11-05 05:00:00,000"}, // + {"2017-11-05 01:00:00,000", "2017-11-05 06:00:00,000"}, // DST jump at 2am US central time + {"2017-11-05 01:00:00,000", "2017-11-05 07:00:00,000"}, // + {"2017-11-05 02:00:00,000", "2017-11-05 08:00:00,000"}, // + {"2017-11-05 03:00:00,000", "2017-11-05 09:00:00,000"}, // + {"2017-11-05 04:00:00,000", "2017-11-05 10:00:00,000"}, // + {"2017-11-05 05:00:00,000", "2017-11-05 11:00:00,000"}, // + {"2017-11-05 06:00:00,000", "2017-11-05 12:00:00,000"}, // + {"2017-11-05 07:00:00,000", "2017-11-05 13:00:00,000"}, // + {"2017-11-05 08:00:00,000", "2017-11-05 14:00:00,000"}, // + {"2017-11-05 09:00:00,000", "2017-11-05 15:00:00,000"}, // + {"2017-11-05 10:00:00,000", "2017-11-05 16:00:00,000"}, // + {"2017-11-05 11:00:00,000", "2017-11-05 17:00:00,000"}, // + {"2017-11-05 12:00:00,000", "2017-11-05 18:00:00,000"}, // + {"2017-11-05 13:00:00,000", "2017-11-05 19:00:00,000"}, // + {"2017-11-05 14:00:00,000", "2017-11-05 20:00:00,000"}, // + {"2017-11-05 15:00:00,000", "2017-11-05 21:00:00,000"}, // + {"2017-11-05 16:00:00,000", "2017-11-05 22:00:00,000"}, // + {"2017-11-05 17:00:00,000", "2017-11-05 23:00:00,000"}, // 24 + {"2017-11-05 18:00:00,000", "2017-11-06 00:00:00,000"}, // + {"2017-11-05 19:00:00,000", "2017-11-06 01:00:00,000"}, // + {"2017-11-05 20:00:00,000", "2017-11-06 02:00:00,000"}, // + {"2017-11-05 21:00:00,000", "2017-11-06 03:00:00,000"}, // + {"2017-11-05 22:00:00,000", "2017-11-06 04:00:00,000"}, // + {"2017-11-05 23:00:00,000", "2017-11-06 05:00:00,000"}, // + {"2017-11-06 00:00:00,000", "2017-11-06 06:00:00,000"}, // + {"2017-11-06 01:00:00,000", "2017-11-06 07:00:00,000"}, // + {"2017-11-06 02:00:00,000", "2017-11-06 08:00:00,000"}, // + {"2017-11-06 03:00:00,000", "2017-11-06 09:00:00,000"}, // + {"2017-11-06 04:00:00,000", "2017-11-06 10:00:00,000"}, // + {"2017-11-06 05:00:00,000", "2017-11-06 11:00:00,000"}, // + }; + + for (int i = 0; i < 36; i++) { + final Date date = calendar.getTime(); + // System.out.println(usCentral.format(date) + ", Fixed: " + fixedUsCentral.format(date.getTime()) + ", utc: + // " + utc.format(date)); + assertEquals(expectedDstAndNoDst[i][0], usCentral.format(date), "SimpleDateFormat TZ=US Central"); + assertEquals(expectedDstAndNoDst[i][1], utc.format(date), "SimpleDateFormat TZ=UTC"); + assertEquals( + expectedDstAndNoDst[i][0], fixedUsCentral.format(date.getTime()), "FixedDateFormat TZ=US Central"); + assertEquals(expectedDstAndNoDst[i][1], fixedUtc.format(date.getTime()), "FixedDateFormat TZ=US Central"); + calendar.add(Calendar.HOUR_OF_DAY, 1); + } + } + + @Test + void testFixedFormat_getDatePatternLengthReturnsDatePatternLength() { + assertEquals("yyyyMMdd".length(), FixedFormat.COMPACT.getDatePatternLength()); + assertEquals("yyyy-MM-dd ".length(), DEFAULT.getDatePatternLength()); + } + + @Test + void testFixedFormat_getDatePatternLengthZeroIfNoDateInPattern() { + assertEquals(0, FixedFormat.ABSOLUTE.getDatePatternLength()); + assertEquals(0, FixedFormat.ABSOLUTE_PERIOD.getDatePatternLength()); + } + + @Test + void testFixedFormat_getDatePatternNullIfNoDateInPattern() { + assertNull(FixedFormat.ABSOLUTE.getDatePattern()); + assertNull(FixedFormat.ABSOLUTE_PERIOD.getDatePattern()); + } + + @Test + void testFixedFormat_getDatePatternReturnsDatePatternIfExists() { + assertEquals("yyyyMMdd", FixedFormat.COMPACT.getDatePattern()); + assertEquals("yyyy-MM-dd ", DEFAULT.getDatePattern()); + } + + @Test + void testFixedFormat_getFastDateFormatNonNullIfDateInPattern() { + assertNotNull(FixedFormat.COMPACT.getFastDateFormat()); + assertNotNull(DEFAULT.getFastDateFormat()); + assertEquals("yyyyMMdd", FixedFormat.COMPACT.getFastDateFormat().getPattern()); + assertEquals("yyyy-MM-dd ", DEFAULT.getFastDateFormat().getPattern()); + } + + @Test + void testFixedFormat_getFastDateFormatNullIfNoDateInPattern() { + assertNull(FixedFormat.ABSOLUTE.getFastDateFormat()); + assertNull(FixedFormat.ABSOLUTE_PERIOD.getFastDateFormat()); + } + + @Test + void testFormatLong() { + final long now = System.currentTimeMillis(); + final long start = now - TimeUnit.HOURS.toMillis(25); + final long end = now + TimeUnit.HOURS.toMillis(25); + for (final FixedFormat format : FixedFormat.values()) { + final String pattern = format.getPattern(); + if (containsNanos(format) || format.getFixedTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat + } + 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(expected, actual, format + "(" + pattern + ")" + "/" + time); + } + } + } + + @Test + void testFormatLong_goingBackInTime() { + final long now = System.currentTimeMillis(); + final long start = now - TimeUnit.HOURS.toMillis(25); + final long end = now + TimeUnit.HOURS.toMillis(25); + for (final FixedFormat format : FixedFormat.values()) { + final String pattern = format.getPattern(); + if (containsNanos(format) || format.getFixedTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat + } + 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(expected, actual, format + "(" + pattern + ")" + "/" + time); + } + } + } + + /** + * This test case validates date pattern before and after DST + * Base Date : 12 Mar 2017 + * Daylight Savings started on : 02:00 AM + */ + @Test + void testFormatLong_goingBackInTime_DST() { + final Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("EST")); + instance.set(2017, 2, 12, 2, 0); + final long now = instance.getTimeInMillis(); + final long start = now - TimeUnit.HOURS.toMillis(1); + final long end = now + TimeUnit.HOURS.toMillis(1); + + for (final FixedFormat format : FixedFormat.values()) { + final String pattern = format.getPattern(); + if (containsNanos(format) || format.getFixedTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat + } + 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(expected, actual, format + "(" + pattern + ")" + "/" + time); + } + } + } + + @Test + void testFormatLongCharArrayInt() { + final long now = System.currentTimeMillis(); + final long start = now - TimeUnit.HOURS.toMillis(25); + final long end = now + TimeUnit.HOURS.toMillis(25); + final char[] buffer = new char[128]; + for (final FixedFormat format : FixedFormat.values()) { + final String pattern = format.getPattern(); + if (containsNanos(format) || format.getFixedTimeZoneFormat() != null) { + // cannot compile precise timestamp formats with SimpleDateFormat + // This format() API not include the TZ + continue; + } + 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(expected, actual, format + "(" + pattern + ")" + "/" + time); + } + } + } + + @Test + void testFormatLongCharArrayInt_goingBackInTime() { + final long now = System.currentTimeMillis(); + final long start = now - TimeUnit.HOURS.toMillis(25); + final long end = now + TimeUnit.HOURS.toMillis(25); + final char[] buffer = new char[128]; + for (final FixedFormat format : FixedFormat.values()) { + final String pattern = format.getPattern(); + if (pattern.endsWith("n") + || pattern.matches(".+n+X*") + || pattern.matches(".+n+Z*") + || format.getFixedTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat + } + 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(expected, actual, format + "(" + pattern + ")" + "/" + time); + } + } + } + + @Test + void testGetFormatReturnsConstructorFixedFormatPattern() { + final FixedDateFormat format = new FixedDateFormat(FixedDateFormat.FixedFormat.ABSOLUTE, TimeZone.getDefault()); + assertSame(FixedDateFormat.FixedFormat.ABSOLUTE.getPattern(), format.getFormat()); + } + + @ParameterizedTest + @MethodSource("org.apache.logging.log4j.core.util.datetime.FixedDateFormat$FixedFormat#values") + @DefaultLocale(language = "en") + void testFixedFormatLength(FixedFormat format) { + LocalDate date = LocalDate.of(2023, 4, 8); + LocalTime time = LocalTime.of(19, 5, 14); + ZoneId zone = ZoneId.of("Europe/Warsaw"); + long epochMillis = ZonedDateTime.of(date, time, zone).toInstant().toEpochMilli(); + MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli(epochMillis, 123_456); + FixedDateFormat formatter = FixedDateFormat.create(format); + + String formatted = formatter.formatInstant(instant); + assertEquals(formatter.getLength(), formatted.length(), formatted); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.java new file mode 100644 index 00000000000..81df39b24b9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.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.core.util.internal; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +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.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.Map; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.SimpleMessageFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +class InternalLoggerRegistryTest { + private LoggerContext loggerContext; + private InternalLoggerRegistry registry; + private MessageFactory messageFactory; + + @BeforeEach + void setUp(TestInfo testInfo) throws NoSuchFieldException, IllegalAccessException { + loggerContext = new LoggerContext(testInfo.getDisplayName()); + final Field registryField = loggerContext.getClass().getDeclaredField("loggerRegistry"); + registryField.setAccessible(true); + registry = (InternalLoggerRegistry) registryField.get(loggerContext); + messageFactory = SimpleMessageFactory.INSTANCE; + } + + @AfterEach + void tearDown() { + if (loggerContext != null) { + loggerContext.stop(); + } + } + + @Test + void testGetLoggerReturnsNullForNonExistentLogger() { + assertNull(registry.getLogger("nonExistent", messageFactory)); + } + + @Test + void testComputeIfAbsentCreatesLogger() { + final Logger logger = registry.computeIfAbsent( + "testLogger", messageFactory, (name, factory) -> new Logger(loggerContext, name, factory) {}); + assertNotNull(logger); + assertEquals("testLogger", logger.getName()); + } + + @Test + void testGetLoggerRetrievesExistingLogger() { + final Logger logger = registry.computeIfAbsent( + "testLogger", messageFactory, (name, factory) -> new Logger(loggerContext, name, factory) {}); + assertSame(logger, registry.getLogger("testLogger", messageFactory)); + } + + @Test + void testHasLoggerReturnsCorrectStatus() { + assertFalse(registry.hasLogger("testLogger", messageFactory)); + registry.computeIfAbsent( + "testLogger", messageFactory, (name, factory) -> new Logger(loggerContext, name, factory) {}); + assertTrue(registry.hasLogger("testLogger", messageFactory)); + } + + @Test + void testExpungeStaleWeakReferenceEntries() { + final String loggerNamePrefix = "testLogger_"; + final int numberOfLoggers = 1000; + + for (int i = 0; i < numberOfLoggers; i++) { + final Logger logger = registry.computeIfAbsent( + loggerNamePrefix + i, + messageFactory, + (name, factory) -> new Logger(loggerContext, name, factory) {}); + logger.info("Using logger {}", logger.getName()); + } + + await().atMost(10, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> { + System.gc(); + registry.computeIfAbsent( + "triggerExpunge", messageFactory, (name, factory) -> new Logger(loggerContext, name, factory) {}); + + final Map>> loggerRefByNameByMessageFactory = + reflectAndGetLoggerMapFromRegistry(); + final Map> loggerRefByName = + loggerRefByNameByMessageFactory.get(messageFactory); + + int unexpectedCount = 0; + for (int i = 0; i < numberOfLoggers; i++) { + if (loggerRefByName.containsKey(loggerNamePrefix + i)) { + unexpectedCount++; + } + } + assertEquals( + 0, unexpectedCount, "Found " + unexpectedCount + " unexpected stale entries for MessageFactory"); + }); + } + + @Test + void testExpungeStaleMessageFactoryEntry() { + final SimpleMessageFactory mockMessageFactory = new SimpleMessageFactory(); + Logger logger = registry.computeIfAbsent( + "testLogger", mockMessageFactory, (name, factory) -> new Logger(loggerContext, name, factory) {}); + logger.info("Using logger {}", logger.getName()); + logger = null; + + await().atMost(10, SECONDS).pollInterval(100, MILLISECONDS).untilAsserted(() -> { + System.gc(); + registry.getLogger("triggerExpunge", mockMessageFactory); + + final Map>> loggerRefByNameByMessageFactory = + reflectAndGetLoggerMapFromRegistry(); + assertNull( + loggerRefByNameByMessageFactory.get(mockMessageFactory), + "Stale MessageFactory entry was not removed from the outer map"); + }); + } + + private Map>> reflectAndGetLoggerMapFromRegistry() + throws NoSuchFieldException, IllegalAccessException { + final Field loggerMapField = registry.getClass().getDeclaredField("loggerRefByNameByMessageFactory"); + loggerMapField.setAccessible(true); + @SuppressWarnings("unchecked") + final Map>> loggerMap = + (Map>>) loggerMapField.get(registry); + return loggerMap; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantNumberFormatterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantNumberFormatterTest.java new file mode 100644 index 00000000000..3521d4d4105 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantNumberFormatterTest.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.util.internal.instant; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.stream.Stream; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class InstantNumberFormatterTest { + + @ParameterizedTest + @MethodSource("testCases") + void should_produce_expected_output( + final InstantFormatter formatter, final Instant instant, final String expectedOutput) { + final String actualOutput = formatter.format(instant); + assertThat(actualOutput).isEqualTo(expectedOutput); + } + + static Stream testCases() { + return Stream.concat( + testCases(1581082727, 982123456, new Object[][] { + {InstantNumberFormatter.EPOCH_SECONDS, "1581082727.982123456"}, + {InstantNumberFormatter.EPOCH_SECONDS_ROUNDED, "1581082727"}, + {InstantNumberFormatter.EPOCH_SECONDS_NANOS, "982123456"}, + {InstantNumberFormatter.EPOCH_MILLIS, "1581082727982.123456"}, + {InstantNumberFormatter.EPOCH_MILLIS_ROUNDED, "1581082727982"}, + {InstantNumberFormatter.EPOCH_MILLIS_NANOS, "123456"}, + {InstantNumberFormatter.EPOCH_NANOS, "1581082727982123456"} + }), + testCases(1591177590, 5000001, new Object[][] { + {InstantNumberFormatter.EPOCH_SECONDS, "1591177590.005000001"}, + {InstantNumberFormatter.EPOCH_SECONDS_ROUNDED, "1591177590"}, + {InstantNumberFormatter.EPOCH_SECONDS_NANOS, "5000001"}, + {InstantNumberFormatter.EPOCH_MILLIS, "1591177590005.000001"}, + {InstantNumberFormatter.EPOCH_MILLIS_ROUNDED, "1591177590005"}, + {InstantNumberFormatter.EPOCH_MILLIS_NANOS, "1"}, + {InstantNumberFormatter.EPOCH_NANOS, "1591177590005000001"} + })); + } + + private static Stream testCases( + long epochSeconds, int epochSecondsNanos, Object[][] formatterAndOutputPairs) { + return Arrays.stream(formatterAndOutputPairs).map(formatterAndOutputPair -> { + final InstantFormatter formatter = (InstantFormatter) formatterAndOutputPair[0]; + final String expectedOutput = (String) formatterAndOutputPair[1]; + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(epochSeconds, epochSecondsNanos); + return new Object[] {formatter, instant, expectedOutput}; + }); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java new file mode 100644 index 00000000000..307511f5bd4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java @@ -0,0 +1,458 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.instant; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.sequencePattern; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.DynamicPatternSequence; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.PatternSequence; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.SecondPatternSequence; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.StaticPatternSequence; +import org.apache.logging.log4j.util.Constants; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class InstantPatternDynamicFormatterTest { + + @ParameterizedTest + @MethodSource("sequencingTestCases") + void sequencing_should_work( + final String pattern, final ChronoUnit thresholdPrecision, final List expectedSequences) { + final List actualSequences = sequencePattern(pattern, thresholdPrecision); + assertThat(actualSequences).isEqualTo(expectedSequences); + } + + static List sequencingTestCases() { + final List testCases = new ArrayList<>(); + + // Merged constants + testCases.add(Arguments.of(":'foo',", ChronoUnit.DAYS, singletonList(new StaticPatternSequence(":foo,")))); + + // `SSSX` should be treated constant for daily updates + testCases.add(Arguments.of("SSSX", ChronoUnit.DAYS, asList(pMilliSec(), pDyn("X")))); + + // `yyyyMMddHHmmssSSSX` instant cache updated hourly + testCases.add(Arguments.of( + "yyyyMMddHHmmssSSSX", + ChronoUnit.HOURS, + asList(pDyn("yyyyMMddHH", ChronoUnit.HOURS), pDyn("mm"), pSec(2, "", 3), pDyn("X")))); + + // `yyyyMMddHHmmssSSSX` instant cache updated per minute + testCases.add(Arguments.of( + "yyyyMMddHHmmssSSSX", + ChronoUnit.MINUTES, + asList(pDyn("yyyyMMddHHmm", ChronoUnit.MINUTES), pSec(2, "", 3), pDyn("X")))); + + // ISO9601 instant cache updated daily + final String iso8601InstantPattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX"; + testCases.add(Arguments.of( + iso8601InstantPattern, + ChronoUnit.DAYS, + asList( + pDyn("yyyy'-'MM'-'dd'T'", ChronoUnit.DAYS), + pDyn("HH':'mm':'", ChronoUnit.MINUTES), + pSec(2, ".", 3), + pDyn("X")))); + + // ISO9601 instant cache updated per minute + testCases.add(Arguments.of( + iso8601InstantPattern, + ChronoUnit.MINUTES, + asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 3), pDyn("X")))); + + // ISO9601 instant cache updated per second + testCases.add(Arguments.of( + iso8601InstantPattern, + ChronoUnit.SECONDS, + asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 3), pDyn("X")))); + + // Seconds and micros + testCases.add(Arguments.of( + "HH:mm:ss.SSSSSS", + ChronoUnit.MINUTES, + asList(pDyn("HH':'mm':'", ChronoUnit.MINUTES), pSec(2, ".", 6)))); + + // Seconds without padding + testCases.add(Arguments.of("s.SSS", ChronoUnit.SECONDS, singletonList(pSec(1, ".", 3)))); + + return testCases; + } + + private static DynamicPatternSequence pDyn(final String singlePattern) { + return new DynamicPatternSequence(singlePattern); + } + + private static DynamicPatternSequence pDyn(final String pattern, final ChronoUnit precision) { + return new DynamicPatternSequence(pattern, precision); + } + + private static SecondPatternSequence pSec(int secondDigits, String separator, int fractionalDigits) { + return new SecondPatternSequence(secondDigits, separator, fractionalDigits); + } + + private static SecondPatternSequence pMilliSec() { + return new SecondPatternSequence(0, "", 3); + } + + @ParameterizedTest + @ValueSource( + strings = { + // Basics + "SSSSSSS", + "SSSSSSSSS", + "n", + "nn", + "N", + "NN", + // Mixed with other stuff + "yyyy-MM-dd HH:mm:ss,SSSSSSS", + "yyyy-MM-dd HH:mm:ss,SSSSSSSS", + "yyyy-MM-dd HH:mm:ss,SSSSSSSSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS" + }) + void should_recognize_patterns_of_nano_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.NANOS); + } + + @ParameterizedTest + @ValueSource( + strings = { + // Basics + "SSSS", + "SSSSS", + "SSSSSS", + // Mixed with other stuff + "yyyy-MM-dd HH:mm:ss,SSSS", + "yyyy-MM-dd HH:mm:ss,SSSSS", + "yyyy-MM-dd HH:mm:ss,SSSSSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSS", + // Single-quoted text containing nanosecond directives + "yyyy-MM-dd'S'HH:mm:ss.SSSSSSXXX", + "yyyy-MM-dd'n'HH:mm:ss.SSSSSSXXX", + "yyyy-MM-dd'N'HH:mm:ss.SSSSSSXXX", + }) + void should_recognize_patterns_of_micro_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.MICROS); + } + + @ParameterizedTest + @ValueSource( + strings = { + // Basics + "SS", + "SSS", + "A", + "AA", + // Mixed with other stuff + "yyyy-MM-dd HH:mm:ss,SS", + "yyyy-MM-dd HH:mm:ss,SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + // Single-quoted text containing nanosecond directives + "yyyy-MM-dd'S'HH:mm:ss.SSSXXX", + "yyyy-MM-dd'n'HH:mm:ss.SSSXXX", + "yyyy-MM-dd'N'HH:mm:ss.SSSXXX", + }) + void should_recognize_patterns_of_milli_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.MILLIS); + } + + @ParameterizedTest + @ValueSource( + strings = { + // Basics + "s", + "ss", + // Mixed with other stuff + "yyyy-MM-dd HH:mm:s", + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ss", + "HH:mm:s", + // Single-quoted text containing nanosecond and millisecond directives + "yyyy-MM-dd'S'HH:mm:ss", + "yyyy-MM-dd'n'HH:mm:ss", + "yyyy-MM-dd'N'HH:mm:ss", + "yyyy-MM-dd'A'HH:mm:ss" + }) + void should_recognize_patterns_of_second_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.SECONDS); + } + + static Stream should_recognize_patterns_of_minute_precision() { + Stream stream = Stream.of( + // Basics + "m", + "mm", + "Z", + "x", + "X", + "O", + "z", + "VV", + // Mixed with other stuff + "yyyy-MM-dd HH:mm", + "yyyy-MM-dd'T'HH:mm", + "HH:mm", + "yyyy-MM-dd HH x", + "yyyy-MM-dd'T'HH XX", + // Single-quoted text containing nanosecond and millisecond directives + "yyyy-MM-dd'S'HH:mm", + "yyyy-MM-dd'n'HH:mm"); + return Constants.JAVA_MAJOR_VERSION > 8 ? Stream.concat(stream, Stream.of("v")) : stream; + } + + @ParameterizedTest + @MethodSource + void should_recognize_patterns_of_minute_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.MINUTES); + } + + @ParameterizedTest + @MethodSource("hourPrecisionPatterns") + void should_recognize_patterns_of_hour_precision(final String pattern) { + assertPatternPrecision(pattern, ChronoUnit.HOURS); + } + + static List hourPrecisionPatterns() { + final List java8Patterns = new ArrayList<>(asList( + // Basics + "H", + "HH", + "a", + "h", + "K", + "k", + "H", + // Mixed with other stuff + "yyyy-MM-dd HH", + "yyyy-MM-dd'T'HH", + "ddHH", + // Single-quoted text containing nanosecond and millisecond directives + "yyyy-MM-dd'S'HH", + "yyyy-MM-dd'n'HH")); + if (Constants.JAVA_MAJOR_VERSION > 8) { + java8Patterns.add("B"); + } + return java8Patterns; + } + + static Stream dynamic_pattern_should_correctly_determine_precision() { + // When no a precise unit is not available, uses the closest smaller unit. + return Stream.of( + Arguments.of("G", ChronoUnit.ERAS), + Arguments.of("u", ChronoUnit.YEARS), + Arguments.of("D", ChronoUnit.DAYS), + Arguments.of("M", ChronoUnit.MONTHS), + Arguments.of("L", ChronoUnit.MONTHS), + Arguments.of("d", ChronoUnit.DAYS), + Arguments.of("Q", ChronoUnit.MONTHS), + Arguments.of("q", ChronoUnit.MONTHS), + Arguments.of("Y", ChronoUnit.YEARS), + Arguments.of("w", ChronoUnit.WEEKS), + Arguments.of("W", ChronoUnit.DAYS), // The month can change in the middle of the week + Arguments.of("F", ChronoUnit.DAYS), // The month can change in the middle of the week + Arguments.of("E", ChronoUnit.DAYS), + Arguments.of("e", ChronoUnit.DAYS), + Arguments.of("c", ChronoUnit.DAYS), + Arguments.of("a", ChronoUnit.HOURS), // Let us round it down + Arguments.of("h", ChronoUnit.HOURS), + Arguments.of("K", ChronoUnit.HOURS), + Arguments.of("k", ChronoUnit.HOURS), + Arguments.of("H", ChronoUnit.HOURS), + Arguments.of("m", ChronoUnit.MINUTES), + Arguments.of("s", ChronoUnit.SECONDS), + Arguments.of("S", ChronoUnit.MILLIS), + Arguments.of("SS", ChronoUnit.MILLIS), + Arguments.of("SSS", ChronoUnit.MILLIS), + Arguments.of("SSSS", ChronoUnit.MICROS), + Arguments.of("SSSSS", ChronoUnit.MICROS), + Arguments.of("SSSSSS", ChronoUnit.MICROS), + Arguments.of("SSSSSSS", ChronoUnit.NANOS), + Arguments.of("SSSSSSSS", ChronoUnit.NANOS), + Arguments.of("SSSSSSSSS", ChronoUnit.NANOS), + Arguments.of("A", ChronoUnit.MILLIS), + Arguments.of("n", ChronoUnit.NANOS), + Arguments.of("N", ChronoUnit.NANOS), + // Time zones can change in the middle of a UTC hour (e.g. India) + Arguments.of("VV", ChronoUnit.MINUTES), + Arguments.of("z", ChronoUnit.MINUTES), + Arguments.of("O", ChronoUnit.MINUTES), + Arguments.of("X", ChronoUnit.MINUTES), + Arguments.of("x", ChronoUnit.MINUTES), + Arguments.of("Z", ChronoUnit.MINUTES)); + } + + @ParameterizedTest + @MethodSource + void dynamic_pattern_should_correctly_determine_precision(String singlePattern, ChronoUnit expectedPrecision) { + assertThat(pDyn(singlePattern).precision).isEqualTo(expectedPrecision); + } + + private static void assertPatternPrecision(final String pattern, final ChronoUnit expectedPrecision) { + final InstantPatternFormatter formatter = + new InstantPatternDynamicFormatter(pattern, Locale.getDefault(), TimeZone.getDefault()); + assertThat(formatter.getPrecision()).as("pattern=`%s`", pattern).isEqualTo(expectedPrecision); + } + + @ParameterizedTest + @MethodSource("formatterInputs") + void output_should_match_DateTimeFormatter( + final String pattern, final Locale locale, final TimeZone timeZone, final MutableInstant instant) { + final String log4jOutput = formatInstant(pattern, locale, timeZone, instant); + final String javaOutput = DateTimeFormatter.ofPattern(pattern, locale) + .withZone(timeZone.toZoneId()) + .format(instant); + assertThat(log4jOutput).isEqualTo(javaOutput); + } + + static Stream formatterInputs() { + return Stream.of( + // Complete list of `FixedDateFormat`-supported patterns in version `2.24.1` + "HH:mm:ss,SSS", + "HH:mm:ss,SSSSSS", + "HH:mm:ss,SSSSSSSSS", + "HH:mm:ss.SSS", + "yyyyMMddHHmmssSSS", + "dd MMM yyyy HH:mm:ss,SSS", + "dd MMM yyyy HH:mm:ss.SSS", + "yyyy-MM-dd HH:mm:ss,SSS", + "yyyy-MM-dd HH:mm:ss,SSSSSS", + "yyyy-MM-dd HH:mm:ss,SSSSSSSSS", + "yyyy-MM-dd HH:mm:ss.SSS", + "yyyyMMdd'T'HHmmss,SSS", + "yyyyMMdd'T'HHmmss.SSS", + "yyyy-MM-dd'T'HH:mm:ss,SSS", + "yyyy-MM-dd'T'HH:mm:ss,SSSx", + "yyyy-MM-dd'T'HH:mm:ss,SSSxx", + "yyyy-MM-dd'T'HH:mm:ss,SSSxxx", + "yyyy-MM-dd'T'HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSS", + "dd/MM/yy HH:mm:ss.SSS", + "dd/MM/yyyy HH:mm:ss.SSS", + // seconds without padding + "s.SSS") + .flatMap(InstantPatternDynamicFormatterTest::formatterInputs); + } + + private static final Random RANDOM = new Random(0); + + private static final Locale[] LOCALES = Locale.getAvailableLocales(); + + private static final TimeZone[] TIME_ZONES = + Arrays.stream(TimeZone.getAvailableIDs()).map(TimeZone::getTimeZone).toArray(TimeZone[]::new); + + static Stream formatterInputs(final String pattern) { + return IntStream.range(0, 100).mapToObj(ignoredIndex -> { + final Locale locale = LOCALES[RANDOM.nextInt(LOCALES.length)]; + final TimeZone timeZone = TIME_ZONES[RANDOM.nextInt(TIME_ZONES.length)]; + final MutableInstant instant = randomInstant(); + return Arguments.of(pattern, locale, timeZone, instant); + }); + } + + private static MutableInstant randomInstant() { + final MutableInstant instant = new MutableInstant(); + // In the 1970's some time zones had sub-minute offsets to UTC, e.g., Africa/Monrovia. + // We will exclude them for tests: + final int minEpochSecond = 315_532_800; // 1980-01-01 01:00:00 + final int maxEpochSecond = 1_621_280_470; // 2021-05-17 21:41:10 + final long epochSecond = minEpochSecond + RANDOM.nextInt(maxEpochSecond - minEpochSecond); + final int epochSecondNano = randomNanos(); + instant.initFromEpochSecond(epochSecond, epochSecondNano); + return instant; + } + + private static int randomNanos() { + int total = 0; + for (int digitIndex = 0; digitIndex < 9; digitIndex++) { + int number; + do { + number = RANDOM.nextInt(10); + } while (digitIndex == 0 && number == 0); + total = total * 10 + number; + } + return total; + } + + private static String formatInstant( + final String pattern, final Locale locale, final TimeZone timeZone, final MutableInstant instant) { + final InstantPatternFormatter formatter = new InstantPatternDynamicFormatter(pattern, locale, timeZone); + final StringBuilder buffer = new StringBuilder(); + formatter.formatTo(buffer, instant); + return buffer.toString(); + } + + @ParameterizedTest + @MethodSource("formatterInputs") + void verify_manually_computed_sub_minute_precision_values( + final String ignoredPattern, + final Locale ignoredLocale, + final TimeZone timeZone, + final MutableInstant instant) { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( + "HH:mm:ss.S-SS-SSS-SSSS-SSSSS-SSSSSS-SSSSSSS-SSSSSSSS-SSSSSSSSS|n") + .withZone(timeZone.toZoneId()); + final String formatterOutput = formatter.format(instant); + final int offsetMillis = timeZone.getOffset(instant.getEpochMillisecond()); + final long adjustedEpochSeconds = (instant.getEpochMillisecond() + offsetMillis) / 1000; + // 86400 seconds per day, 3600 seconds per hour + final int local_H = (int) ((adjustedEpochSeconds % 86400L) / 3600L); + final int local_m = (int) ((adjustedEpochSeconds / 60) % 60); + final int local_s = (int) (adjustedEpochSeconds % 60); + final int local_S = instant.getNanoOfSecond() / 100000000; + final int local_SS = instant.getNanoOfSecond() / 10000000; + final int local_SSS = instant.getNanoOfSecond() / 1000000; + final int local_SSSS = instant.getNanoOfSecond() / 100000; + final int local_SSSSS = instant.getNanoOfSecond() / 10000; + final int local_SSSSSS = instant.getNanoOfSecond() / 1000; + final int local_SSSSSSS = instant.getNanoOfSecond() / 100; + final int local_SSSSSSSS = instant.getNanoOfSecond() / 10; + final int local_SSSSSSSSS = instant.getNanoOfSecond(); + final int local_n = instant.getNanoOfSecond(); + final String output = String.format( + "%02d:%02d:%02d.%d-%d-%d-%d-%d-%d-%d-%d-%d|%d", + local_H, + local_m, + local_s, + local_S, + local_SS, + local_SSS, + local_SSSS, + local_SSSSS, + local_SSSSSS, + local_SSSSSSS, + local_SSSSSSSS, + local_SSSSSSSSS, + local_n); + assertThat(output).isEqualTo(formatterOutput); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternThreadLocalCachedFormatterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternThreadLocalCachedFormatterTest.java new file mode 100644 index 00000000000..f4435c0086d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternThreadLocalCachedFormatterTest.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.internal.instant; + +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.time.temporal.ChronoUnit; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.Issue; + +class InstantPatternThreadLocalCachedFormatterTest { + + private static final Locale LOCALE = Locale.getDefault(); + + private static final TimeZone TIME_ZONE = TimeZone.getDefault(); + + @ParameterizedTest + @MethodSource("getterTestCases") + void getters_should_work( + final Function cachedFormatterSupplier, + final String pattern, + final Locale locale, + final TimeZone timeZone) { + final InstantPatternDynamicFormatter dynamicFormatter = + new InstantPatternDynamicFormatter(pattern, locale, timeZone); + final InstantPatternThreadLocalCachedFormatter cachedFormatter = + cachedFormatterSupplier.apply(dynamicFormatter); + assertThat(cachedFormatter.getPattern()).isEqualTo(pattern); + assertThat(cachedFormatter.getLocale()).isEqualTo(locale); + assertThat(cachedFormatter.getTimeZone()).isEqualTo(timeZone); + } + + static Object[][] getterTestCases() { + + // Choosing two different locale & time zone pairs to ensure having one that doesn't match the system default + final Locale locale1 = Locale.forLanguageTag("nl_NL"); + final Locale locale2 = Locale.forLanguageTag("tr_TR"); + final String[] timeZoneIds = TimeZone.getAvailableIDs(); + final int timeZone1IdIndex = new Random(0).nextInt(timeZoneIds.length); + final int timeZone2IdIndex = (timeZone1IdIndex + 1) % timeZoneIds.length; + final TimeZone timeZone1 = TimeZone.getTimeZone(timeZoneIds[timeZone1IdIndex]); + final TimeZone timeZone2 = TimeZone.getTimeZone(timeZoneIds[timeZone2IdIndex]); + + // Create test cases + return new Object[][] { + // For `ofMilliPrecision()` + { + (Function) + InstantPatternThreadLocalCachedFormatter::ofMilliPrecision, + "HH:mm.SSS", + locale1, + timeZone1 + }, + { + (Function) + InstantPatternThreadLocalCachedFormatter::ofMilliPrecision, + "HH:mm.SSS", + locale2, + timeZone2 + }, + // For `ofSecondPrecision()` + { + (Function) + InstantPatternThreadLocalCachedFormatter::ofSecondPrecision, + "yyyy", + locale1, + timeZone1 + }, + { + (Function) + InstantPatternThreadLocalCachedFormatter::ofSecondPrecision, + "yyyy", + locale2, + timeZone2 + } + }; + } + + @ParameterizedTest + @ValueSource(strings = {"SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS", "n", "N"}) + void ofMilliPrecision_should_fail_on_inconsistent_precision(final String subMilliPattern) { + final InstantPatternDynamicFormatter dynamicFormatter = + new InstantPatternDynamicFormatter(subMilliPattern, LOCALE, TIME_ZONE); + assertThatThrownBy(() -> InstantPatternThreadLocalCachedFormatter.ofMilliPrecision(dynamicFormatter)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "instant formatter `%s` is of `%s` precision, whereas the requested cache precision is `%s`", + dynamicFormatter, dynamicFormatter.getPrecision(), ChronoUnit.MILLIS); + } + + @ParameterizedTest + @ValueSource(strings = {"SSS", "s", "ss", "m", "mm", "H", "HH"}) + void ofMilliPrecision_should_truncate_precision_to_milli(final String superMilliPattern) { + final InstantPatternDynamicFormatter dynamicFormatter = + new InstantPatternDynamicFormatter(superMilliPattern, LOCALE, TIME_ZONE); + final InstantPatternThreadLocalCachedFormatter cachedFormatter = + InstantPatternThreadLocalCachedFormatter.ofMilliPrecision(dynamicFormatter); + assertThat(cachedFormatter.getPrecision()).isEqualTo(ChronoUnit.MILLIS); + assertThat(cachedFormatter.getPrecision().compareTo(dynamicFormatter.getPrecision())) + .isLessThanOrEqualTo(0); + } + + @ParameterizedTest + @ValueSource( + strings = {"S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS", "n", "N", "A"}) + void ofSecondPrecision_should_fail_on_inconsistent_precision(final String subSecondPattern) { + final InstantPatternDynamicFormatter dynamicFormatter = + new InstantPatternDynamicFormatter(subSecondPattern, LOCALE, TIME_ZONE); + assertThatThrownBy(() -> InstantPatternThreadLocalCachedFormatter.ofSecondPrecision(dynamicFormatter)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "instant formatter `%s` is of `%s` precision, whereas the requested cache precision is `%s`", + dynamicFormatter, dynamicFormatter.getPrecision(), ChronoUnit.SECONDS); + } + + @ParameterizedTest + @ValueSource(strings = {"s", "ss", "m", "mm", "H", "HH"}) + void ofSecondPrecision_should_truncate_precision_to_second(final String superSecondPattern) { + final InstantPatternDynamicFormatter dynamicFormatter = + new InstantPatternDynamicFormatter(superSecondPattern, LOCALE, TIME_ZONE); + final InstantPatternThreadLocalCachedFormatter cachedFormatter = + InstantPatternThreadLocalCachedFormatter.ofSecondPrecision(dynamicFormatter); + assertThat(cachedFormatter.getPrecision()).isEqualTo(ChronoUnit.SECONDS); + assertThat(cachedFormatter.getPrecision().compareTo(dynamicFormatter.getPrecision())) + .isLessThanOrEqualTo(0); + } + + private static final MutableInstant INSTANT0 = createInstant(0, 0); + + @Test + void ofMilliPrecision_should_cache() { + + // Mock a pattern formatter + final InstantPatternFormatter patternFormatter = mock(InstantPatternFormatter.class); + when(patternFormatter.getPrecision()).thenReturn(ChronoUnit.MILLIS); + + // Configure the pattern formatter for the 1st instant + final Instant instant1 = INSTANT0; + final String output1 = "instant1"; + doAnswer(invocation -> { + final StringBuilder buffer = invocation.getArgument(0); + buffer.append(output1); + return null; + }) + .when(patternFormatter) + .formatTo(any(StringBuilder.class), eq(instant1)); + + // Create a 2nd distinct instant that shares the same milliseconds with the 1st instant. + // That is, the 2nd instant should trigger a cache hit. + final MutableInstant instant2 = offsetInstant(instant1, 0, 1); + assertThat(instant1.getEpochMillisecond()).isEqualTo(instant2.getEpochMillisecond()); + assertThat(instant1).isNotEqualTo(instant2); + + // Configure the pattern for a 3rd distinct instant. + // The 3rd instant should be of different milliseconds with the 1st (and 2nd) instants to trigger a cache miss. + final MutableInstant instant3 = offsetInstant(instant2, 1, 0); + assertThat(instant2.getEpochMillisecond()).isNotEqualTo(instant3.getEpochMillisecond()); + final String output3 = "instant3"; + doAnswer(invocation -> { + final StringBuilder buffer = invocation.getArgument(0); + buffer.append(output3); + return null; + }) + .when(patternFormatter) + .formatTo(any(StringBuilder.class), eq(instant3)); + + // Create a 4th distinct instant that shares the same milliseconds with the 3rd instant. + // That is, the 4th instant should trigger a cache hit. + final MutableInstant instant4 = offsetInstant(instant3, 0, 1); + assertThat(instant3.getEpochMillisecond()).isEqualTo(instant4.getEpochMillisecond()); + assertThat(instant3).isNotEqualTo(instant4); + + // Create the cached formatter and verify its output + final InstantFormatter cachedFormatter = + InstantPatternThreadLocalCachedFormatter.ofMilliPrecision(patternFormatter); + assertThat(cachedFormatter.format(instant1)).isEqualTo(output1); // Cache miss + assertThat(cachedFormatter.format(instant2)).isEqualTo(output1); // Cache hit + assertThat(cachedFormatter.format(instant2)).isEqualTo(output1); // Repeated cache hit + assertThat(cachedFormatter.format(instant3)).isEqualTo(output3); // Cache miss + assertThat(cachedFormatter.format(instant4)).isEqualTo(output3); // Cache hit + assertThat(cachedFormatter.format(instant4)).isEqualTo(output3); // Repeated cache hit + + // Verify the pattern formatter interaction + verify(patternFormatter).getPrecision(); + verify(patternFormatter).formatTo(any(StringBuilder.class), eq(instant1)); + verify(patternFormatter).formatTo(any(StringBuilder.class), eq(instant3)); + verifyNoMoreInteractions(patternFormatter); + } + + @Test + void ofSecondPrecision_should_cache() { + + // Mock a pattern formatter + final InstantPatternFormatter patternFormatter = mock(InstantPatternFormatter.class); + when(patternFormatter.getPrecision()).thenReturn(ChronoUnit.SECONDS); + + // Configure the pattern formatter for the 1st instant + final Instant instant1 = INSTANT0; + final String output1 = "instant1"; + doAnswer(invocation -> { + final StringBuilder buffer = invocation.getArgument(0); + buffer.append(output1); + return null; + }) + .when(patternFormatter) + .formatTo(any(StringBuilder.class), eq(instant1)); + + // Create a 2nd distinct instant that shares the same seconds with the 1st instant. + // That is, the 2nd instant should trigger a cache hit. + final MutableInstant instant2 = offsetInstant(instant1, 1, 0); + assertThat(instant1.getEpochSecond()).isEqualTo(instant2.getEpochSecond()); + assertThat(instant1).isNotEqualTo(instant2); + + // Configure the pattern for a 3rd distinct instant. + // The 3rd instant should be of different seconds with the 1st (and 2nd) instants to trigger a cache miss. + final MutableInstant instant3 = offsetInstant(instant2, 1_000, 0); + assertThat(instant2.getEpochSecond()).isNotEqualTo(instant3.getEpochSecond()); + final String output3 = "instant3"; + doAnswer(invocation -> { + final StringBuilder buffer = invocation.getArgument(0); + buffer.append(output3); + return null; + }) + .when(patternFormatter) + .formatTo(any(StringBuilder.class), eq(instant3)); + + // Create a 4th distinct instant that shares the same seconds with the 3rd instant. + // That is, the 4th instant should trigger a cache hit. + final MutableInstant instant4 = offsetInstant(instant3, 1, 0); + assertThat(instant3.getEpochSecond()).isEqualTo(instant4.getEpochSecond()); + assertThat(instant3).isNotEqualTo(instant4); + + // Create the cached formatter and verify its output + final InstantFormatter cachedFormatter = + InstantPatternThreadLocalCachedFormatter.ofSecondPrecision(patternFormatter); + assertThat(cachedFormatter.format(instant1)).isEqualTo(output1); // Cache miss + assertThat(cachedFormatter.format(instant2)).isEqualTo(output1); // Cache hit + assertThat(cachedFormatter.format(instant2)).isEqualTo(output1); // Repeated cache hit + assertThat(cachedFormatter.format(instant3)).isEqualTo(output3); // Cache miss + assertThat(cachedFormatter.format(instant4)).isEqualTo(output3); // Cache hit + assertThat(cachedFormatter.format(instant4)).isEqualTo(output3); // Repeated cache hit + + // Verify the pattern formatter interaction + verify(patternFormatter).getPrecision(); + verify(patternFormatter).formatTo(any(StringBuilder.class), eq(instant1)); + verify(patternFormatter).formatTo(any(StringBuilder.class), eq(instant3)); + verifyNoMoreInteractions(patternFormatter); + } + + private static MutableInstant offsetInstant( + final Instant instant, final long epochMillisOffset, final int epochMillisNanosOffset) { + final long epochMillis = Math.addExact(instant.getEpochMillisecond(), epochMillisOffset); + final int epochMillisNanos = Math.addExact(instant.getNanoOfMillisecond(), epochMillisNanosOffset); + return createInstant(epochMillis, epochMillisNanos); + } + + private static MutableInstant createInstant(final long epochMillis, final int epochMillisNanos) { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli(epochMillis, epochMillisNanos); + return instant; + } + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/3792") + void should_be_thread_safe() throws Exception { + // Instead of randomly testing the thread safety, we test that the current implementation does not + // cache results across threads. + // + // Modify this test if the implementation changes in the future. + final InstantPatternFormatter patternFormatter = mock(InstantPatternFormatter.class); + when(patternFormatter.getPrecision()).thenReturn(ChronoUnit.MILLIS); + + final Instant instant = INSTANT0; + final String output = "thread-output"; + doAnswer(invocation -> { + StringBuilder buffer = invocation.getArgument(0); + buffer.append(output) + .append('-') + .append(Thread.currentThread().getName()); + return null; + }) + .when(patternFormatter) + .formatTo(any(StringBuilder.class), eq(instant)); + + final InstantFormatter cachedFormatter = + InstantPatternThreadLocalCachedFormatter.ofMilliPrecision(patternFormatter); + + final int threadCount = 2; + for (int i = 0; i < threadCount; i++) { + formatOnNewThread(cachedFormatter, instant, output); + } + verify(patternFormatter, times(threadCount)).formatTo(any(StringBuilder.class), eq(instant)); + } + + private static void formatOnNewThread( + final InstantFormatter formatter, final Instant instant, final String expectedOutput) + throws ExecutionException, InterruptedException { + ExecutorService executor = newSingleThreadExecutor(); + try { + executor.submit(() -> { + String formatted = formatter.format(instant); + assertThat(formatted) + .isEqualTo(expectedOutput + "-" + + Thread.currentThread().getName()); + }) + .get(); + } finally { + executor.shutdown(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/message/MutableLogEventWithReusableParamMsgTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/message/MutableLogEventWithReusableParamMsgTest.java new file mode 100644 index 00000000000..b0ad37ffb3e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/message/MutableLogEventWithReusableParamMsgTest.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.message; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsSame.sameInstance; + +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.junit.jupiter.api.Test; + +/** + * LOG4J2-1409 + */ +// test must be in log4j-core but in org.apache.logging.log4j.message package because it calls package-private methods +class MutableLogEventWithReusableParamMsgTest { + @Test + void testInteractionWithReusableParameterizedMessage() { + final MutableLogEvent evt = new MutableLogEvent(); + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + 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(); + + final Message mementoMessage = evt.memento(); + final Message mementoMessageSecondInvocation = evt.memento(); + // MutableLogEvent.memento should be cached + assertThat(mementoMessage, sameInstance(mementoMessageSecondInvocation)); + } +} 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..16509890998 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncAppenderExceptionHandlingTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml new file mode 100644 index 00000000000..32a9dda46cc --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml @@ -0,0 +1,36 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + 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..3ab63c4a1a7 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigErrorOnFormat.xml @@ -0,0 +1,35 @@ + + + + + + + %d %p %c{1.} [%t] %m %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..f42ff3aa216 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest2.xml @@ -0,0 +1,36 @@ + + + + + + + %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..bb5ff6983b0 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest4.xml @@ -0,0 +1,44 @@ + + + + + + + %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..c0d4667a892 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigThreadContextTest.xml @@ -0,0 +1,66 @@ + + + + + + + %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..db7be531ce0 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConsoleTest.xml @@ -0,0 +1,32 @@ + + + + + + + %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..30bfa3e9c17 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerCustomSelectorLocationTest.xml @@ -0,0 +1,33 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + 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..68c6a04c25d --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerDefaultLocationTest.xml @@ -0,0 +1,36 @@ + + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerLocationTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerLocationTest.xml new file mode 100644 index 00000000000..18d69dbd9bb --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerLocationTest.xml @@ -0,0 +1,33 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + 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..fd2ee89ec7e --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerTest.xml @@ -0,0 +1,33 @@ + + + + + + + %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..0bd870c5715 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerThreadContextTest.xml @@ -0,0 +1,35 @@ + + + + + + + %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..9cd69bd653f --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerTimestampMessageTest.xml @@ -0,0 +1,33 @@ + + + + + + + %d{UNIX_MILLIS} %m%n + + + + + + + + + + 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..ddfacacde08 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncRootLoggerDefaultLocationTest.xml b/log4j-core-test/src/test/resources/AsyncRootLoggerDefaultLocationTest.xml new file mode 100644 index 00000000000..71ccbd7f323 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncRootLoggerDefaultLocationTest.xml @@ -0,0 +1,34 @@ + + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + 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..7d1802397b4 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.properties b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.properties new file mode 100644 index 00000000000..e8a76f6fda5 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.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. +# + +strategy.type = AsyncWaitStrategyFactory +strategy.class = org.apache.logging.log4j.core.async.AsyncWaitStrategyFactoryConfigTest$YieldingWaitStrategyFactory 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..5f82233848d --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + 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..28ece5ffa93 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + 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..8445e8949fd --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml new file mode 100644 index 00000000000..2e9b195f5c8 --- /dev/null +++ b/log4j-core-test/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml new file mode 100644 index 00000000000..6712570ed95 --- /dev/null +++ b/log4j-core-test/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml new file mode 100644 index 00000000000..c53500891b6 --- /dev/null +++ b/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml new file mode 100644 index 00000000000..4d6534ad8d2 --- /dev/null +++ b/log4j-core-test/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/ContextMapLookupTest.xml b/log4j-core-test/src/test/resources/ContextMapLookupTest.xml new file mode 100644 index 00000000000..6671b18f9cb --- /dev/null +++ b/log4j-core-test/src/test/resources/ContextMapLookupTest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/FlumeFuncTest.xml b/log4j-core-test/src/test/resources/FlumeFuncTest.xml new file mode 100644 index 00000000000..029f4bd6ad2 --- /dev/null +++ b/log4j-core-test/src/test/resources/FlumeFuncTest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/GelfLayout2Test.xml b/log4j-core-test/src/test/resources/GelfLayout2Test.xml new file mode 100644 index 00000000000..11d8355c850 --- /dev/null +++ b/log4j-core-test/src/test/resources/GelfLayout2Test.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..cfb1504d8e4 --- /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..75455e2fdd8 --- /dev/null +++ b/log4j-core-test/src/test/resources/GelfLayoutPatternSelectorTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.java b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.java new file mode 100644 index 00000000000..c3223781f49 --- /dev/null +++ b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeAnnotations.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 example; + +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.config.plugins.PluginVisitorStrategy; +import org.apache.logging.log4j.core.config.plugins.validation.Constraint; +import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; +import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; + +/** + * Fake constraint and plugin visitor that are accessed through reflection. + */ +public class FakeAnnotations { + + @Constraint(FakeConstraintValidator.class) + public @interface FakeConstraint {} + + public static class FakeConstraintValidator implements ConstraintValidator { + @Override + public void initialize(FakeConstraint annotation) {} + + @Override + public boolean isValid(String name, Object value) { + return false; + } + } + + @PluginVisitorStrategy(FakePluginVisitor.class) + public @interface FakeAnnotation {} + + public static class FakePluginVisitor implements PluginVisitor { + + @Override + public PluginVisitor setAnnotation(Annotation annotation) { + return null; + } + + @Override + public PluginVisitor setAliases(String... aliases) { + return null; + } + + @Override + public PluginVisitor setStrSubstitutor(StrSubstitutor substitutor) { + return null; + } + + @Override + public PluginVisitor setMember(Member member) { + return null; + } + + @Override + public Object visit(Configuration configuration, Node node, LogEvent event, StringBuilder log) { + return null; + } + + @Override + public PluginVisitor setConversionType(Class conversionType) { + return null; + } + } +} diff --git a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java new file mode 100644 index 00000000000..0b15cb4ecc1 --- /dev/null +++ b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.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 example; + +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.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@Plugin(name = "FakePatternConverter", category = PatternConverter.CATEGORY) +@ConverterKeys({"f", "fake"}) +public final class FakeConverter extends LogEventPatternConverter { + + private FakeConverter(@Nullable final Configuration config, @Nullable final String[] options) { + super("Fake", "fake"); + } + + public static FakeConverter newInstance( + @Nullable final Configuration config, @Nullable final String[] options) { + return new FakeConverter(config, options); + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + toAppendTo.append("FakeConverter: ").append(event.getMessage().getFormattedMessage()); + } +} diff --git a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.java b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.java new file mode 100644 index 00000000000..90b872c0a31 --- /dev/null +++ b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakePlugin.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 example; + +import java.io.Serializable; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +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.PluginAliases; +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.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.PluginLoggerContext; +import org.apache.logging.log4j.core.config.plugins.PluginNode; +import org.apache.logging.log4j.core.config.plugins.PluginValue; + +/** + * Test plugin class for unit tests. + */ +@Plugin(name = "Fake", category = "Test") +@PluginAliases({"AnotherFake", "StillFake"}) +public class FakePlugin { + + @Plugin(name = "Nested", category = "Test") + public static class Nested {} + + @PluginFactory + public static FakePlugin newPlugin( + @PluginAttribute("attribute") int attribute, + @PluginElement("layout") Layout layout, + @PluginConfiguration Configuration config, + @PluginNode Node node, + @PluginLoggerContext LoggerContext loggerContext, + @PluginValue("value") String value) { + return null; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private int attribute; + + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private int attributeWithoutPublicSetterButWithSuppressAnnotation; + + @PluginElement("layout") + private Layout layout; + + @PluginConfiguration + private Configuration config; + + @PluginNode + private Node node; + + @PluginLoggerContext + private LoggerContext loggerContext; + + @PluginValue("value") + private String value; + + @Override + public FakePlugin build() { + return null; + } + } +} diff --git a/log4j-core-test/src/test/resources/InvalidConfig.xml b/log4j-core-test/src/test/resources/InvalidConfig.xml new file mode 100644 index 00000000000..5318d3a0243 --- /dev/null +++ b/log4j-core-test/src/test/resources/InvalidConfig.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/InvalidXML.xml b/log4j-core-test/src/test/resources/InvalidXML.xml new file mode 100644 index 00000000000..12bc2750cf7 --- /dev/null +++ b/log4j-core-test/src/test/resources/InvalidXML.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/JeroMqAppenderTest.xml b/log4j-core-test/src/test/resources/JeroMqAppenderTest.xml new file mode 100644 index 00000000000..b3ad0e6d24b --- /dev/null +++ b/log4j-core-test/src/test/resources/JeroMqAppenderTest.xml @@ -0,0 +1,31 @@ + + + + + + tcp://*:0 + ipc://info-topic + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/JmsAppenderTest.xml b/log4j-core-test/src/test/resources/JmsAppenderTest.xml new file mode 100644 index 00000000000..5773107e817 --- /dev/null +++ b/log4j-core-test/src/test/resources/JmsAppenderTest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/JndiRestrictedLookup.ldif b/log4j-core-test/src/test/resources/JndiRestrictedLookup.ldif new file mode 100644 index 00000000000..35daf435c8c --- /dev/null +++ b/log4j-core-test/src/test/resources/JndiRestrictedLookup.ldif @@ -0,0 +1,4 @@ +dn: dc=apache,dc=org +objectClass: domain +objectClass: top +dc: apache \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/JsonCompleteFileAppenderTest.xml b/log4j-core-test/src/test/resources/JsonCompleteFileAppenderTest.xml new file mode 100644 index 00000000000..1fac225cbad --- /dev/null +++ b/log4j-core-test/src/test/resources/JsonCompleteFileAppenderTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/KafkaAppenderCloseTimeoutTest.xml b/log4j-core-test/src/test/resources/KafkaAppenderCloseTimeoutTest.xml similarity index 75% rename from log4j-core/src/test/resources/KafkaAppenderCloseTimeoutTest.xml rename to log4j-core-test/src/test/resources/KafkaAppenderCloseTimeoutTest.xml index e58cb17fe21..4b1b65932df 100644 --- a/log4j-core/src/test/resources/KafkaAppenderCloseTimeoutTest.xml +++ b/log4j-core-test/src/test/resources/KafkaAppenderCloseTimeoutTest.xml @@ -1,26 +1,26 @@ - localhost:9092 1000 + @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/KafkaAppenderTest.xml b/log4j-core-test/src/test/resources/KafkaAppenderTest.xml new file mode 100644 index 00000000000..365236ea149 --- /dev/null +++ b/log4j-core-test/src/test/resources/KafkaAppenderTest.xml @@ -0,0 +1,66 @@ + + + + + + localhost:9092 + 1000 + + + + localhost:9092 + 1000 + + + + localhost:9092 + false + + + + 1000 + localhost:9092 + + + + 1000 + localhost:9092 + + + + 1000 + fakeLocalhost:9092 + + + + 1000 + localhost:9092 + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/KafkaManagerProducerThreadLeakTest.xml b/log4j-core-test/src/test/resources/KafkaManagerProducerThreadLeakTest.xml new file mode 100644 index 00000000000..929a6fa2ec8 --- /dev/null +++ b/log4j-core-test/src/test/resources/KafkaManagerProducerThreadLeakTest.xml @@ -0,0 +1,31 @@ + + + + + + localhost:9092 + 1000 + + + + + + + + + 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..5a54f65e8fd --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J-2195/log4j2.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml new file mode 100644 index 00000000000..2cbeb135a2d --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml @@ -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. +# +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 diff --git a/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml new file mode 100644 index 00000000000..5428ca13b79 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml @@ -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. +# +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 diff --git a/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json new file mode 100644 index 00000000000..7d980f26f9a --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json @@ -0,0 +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 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..d8b29f2cb1e --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotatedClass.java.source b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotatedClass.java.source new file mode 100644 index 00000000000..f9efde30f81 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotatedClass.java.source @@ -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. + */ + +@MyAnnotation +public class MyAnnotatedClass { +} diff --git a/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotation.java.source b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotation.java.source new file mode 100644 index 00000000000..9f37b5aebc6 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotation.java.source @@ -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. + */ + +public @interface MyAnnotation { +} diff --git a/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotationProcessor.java.source b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotationProcessor.java.source new file mode 100644 index 00000000000..dbd0fb8acbe --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-3609/MyAnnotationProcessor.java.source @@ -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. + */ + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.TypeElement; +import java.util.Set; + +public class MyAnnotationProcessor extends AbstractProcessor { + @Override + public boolean process(final Set annotations, final RoundEnvironment roundEnv) { + // trivial annotation processor claims nothing and does nothing + return false; + } +} diff --git a/log4j-core-test/src/test/resources/LOG4J2-3609/MyEmptySubClass.java.source b/log4j-core-test/src/test/resources/LOG4J2-3609/MyEmptySubClass.java.source new file mode 100644 index 00000000000..377e47f556b --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-3609/MyEmptySubClass.java.source @@ -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. + */ + +public class MyEmptySubClass extends MyAnnotatedClass { +} 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..7c274a68cc6 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-739.xml @@ -0,0 +1,44 @@ + + + + + app + %d{yyyy-MM-dd HH:mm:ss.SSS} | %-5.5p | %-10.10t | %-20.20C:%-5.5L | %msg%n + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/LOG4J2-807.xml b/log4j-core-test/src/test/resources/LOG4J2-807.xml new file mode 100644 index 00000000000..3503762005d --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-807.xml @@ -0,0 +1,31 @@ + + + + + + + %d %-5p [%c{1.}] %m%n + + + + + + + + + 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..af9253b64ef --- /dev/null +++ b/log4j-core-test/src/test/resources/LoggerLevelAppenderTest.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. +# + +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.name = first +appender.first.type = List +appender.second.name = second +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..39cf7688e44 --- /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/src/test/resources/META-INF/LICENSE b/log4j-core-test/src/test/resources/META-INF/LICENSE similarity index 100% rename from log4j-core/src/test/resources/META-INF/LICENSE rename to log4j-core-test/src/test/resources/META-INF/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-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml new file mode 100644 index 00000000000..b159b21a156 --- /dev/null +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml @@ -0,0 +1,34 @@ + + + + + + + %location: %m%n + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml new file mode 100644 index 00000000000..bd10ac9f89e --- /dev/null +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml @@ -0,0 +1,34 @@ + + + + + + + %m%n + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml new file mode 100644 index 00000000000..5e16189aeaf --- /dev/null +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml @@ -0,0 +1,34 @@ + + + + + + + %m%n + + + + + + + + + + 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..a48d1167a59 --- /dev/null +++ b/log4j-core-test/src/test/resources/NanoTimeToFileTest.xml @@ -0,0 +1,33 @@ + + + + + + + %N AND %nano AND %m%n + + + + + + + + + + diff --git a/log4j-core/src/test/resources/README.md b/log4j-core-test/src/test/resources/README.md similarity index 100% rename from log4j-core/src/test/resources/README.md rename to log4j-core-test/src/test/resources/README.md diff --git a/log4j-core-test/src/test/resources/RandomAccessFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/RandomAccessFileAppenderLocationTest.xml new file mode 100644 index 00000000000..38a76f0a89a --- /dev/null +++ b/log4j-core-test/src/test/resources/RandomAccessFileAppenderLocationTest.xml @@ -0,0 +1,35 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/RandomAccessFileAppenderTest.xml b/log4j-core-test/src/test/resources/RandomAccessFileAppenderTest.xml new file mode 100644 index 00000000000..146dd390cbe --- /dev/null +++ b/log4j-core-test/src/test/resources/RandomAccessFileAppenderTest.xml @@ -0,0 +1,35 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + 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..cdea277f716 --- /dev/null +++ b/log4j-core-test/src/test/resources/ReliabilityStrategyTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml new file mode 100644 index 00000000000..05f3c6dd32c --- /dev/null +++ b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties similarity index 95% rename from log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties rename to log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties index 295031e2c85..3ddd27d9334 100644 --- a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties +++ b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = error name = PropertiesConfigTest diff --git a/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml new file mode 100644 index 00000000000..9e4f8445528 --- /dev/null +++ b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationTest.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/RollingRandomAccessFileAppenderTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderTest.xml new file mode 100644 index 00000000000..fcea9c717c9 --- /dev/null +++ b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderTest.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/RootLoggerLevelAppenderTest.properties b/log4j-core-test/src/test/resources/RootLoggerLevelAppenderTest.properties new file mode 100644 index 00000000000..069551cdddf --- /dev/null +++ b/log4j-core-test/src/test/resources/RootLoggerLevelAppenderTest.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. +# + +status = off +name = RootLoggerLevelAppenderTest +rootLogger = INFO,app +appender.app.name = app +appender.app.type = List diff --git a/log4j-core-test/src/test/resources/SequenceNumberPatternConverterTest.yaml b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterTest.yaml new file mode 100644 index 00000000000..330d31415e6 --- /dev/null +++ b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterTest.yaml @@ -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. +# +Configuration: + status: OFF + name: SequenceNumberPatternConverterTest + + Appenders: + List: + name: List + PatternLayout: + pattern: '%sn' + + Loggers: + Root: + level: INFO + AppenderRef: + ref: List diff --git a/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yaml b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yaml new file mode 100644 index 00000000000..7bb9a692925 --- /dev/null +++ b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yaml @@ -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. +# +Configuration: + status: OFF + name: SequenceNumberPatternConverterTest + + Appenders: + List: + - name: Padded + PatternLayout: + pattern: '%03sn' + + Loggers: + Root: + level: INFO + AppenderRef: + - ref: Padded diff --git a/log4j-core-test/src/test/resources/ShutdownCallbackRegistryTest.xml b/log4j-core-test/src/test/resources/ShutdownCallbackRegistryTest.xml new file mode 100644 index 00000000000..cd56b9638ab --- /dev/null +++ b/log4j-core-test/src/test/resources/ShutdownCallbackRegistryTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..ff29f544df1 --- /dev/null +++ b/log4j-core-test/src/test/resources/SmtpAppenderAsyncTest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/XmlCompactFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlCompactFileAppenderTest.xml new file mode 100644 index 00000000000..e26866a022f --- /dev/null +++ b/log4j-core-test/src/test/resources/XmlCompactFileAppenderTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/XmlCompleteFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlCompleteFileAppenderTest.xml new file mode 100644 index 00000000000..c1ae32da492 --- /dev/null +++ b/log4j-core-test/src/test/resources/XmlCompleteFileAppenderTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/XmlConfigurationSecurity.xml b/log4j-core-test/src/test/resources/XmlConfigurationSecurity.xml new file mode 100644 index 00000000000..4a648ad5e5b --- /dev/null +++ b/log4j-core-test/src/test/resources/XmlConfigurationSecurity.xml @@ -0,0 +1,33 @@ + + + +%dtd;]> + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/XmlFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlFileAppenderTest.xml new file mode 100644 index 00000000000..4860f5a42bc --- /dev/null +++ b/log4j-core-test/src/test/resources/XmlFileAppenderTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/__files/log4j-test1.xml b/log4j-core-test/src/test/resources/__files/log4j-test1.xml new file mode 100644 index 00000000000..4d8eead33b3 --- /dev/null +++ b/log4j-core-test/src/test/resources/__files/log4j-test1.xml @@ -0,0 +1,57 @@ + + + + + target/test-xml.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + 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-test/src/test/resources/appender/rolling/RollingAppenderDirectCronTest.xml b/log4j-core-test/src/test/resources/appender/rolling/RollingAppenderDirectCronTest.xml new file mode 100644 index 00000000000..f9017646a66 --- /dev/null +++ b/log4j-core-test/src/test/resources/appender/rolling/RollingAppenderDirectCronTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-badfilename.xml b/log4j-core-test/src/test/resources/bad/log4j-badfilename.xml new file mode 100644 index 00000000000..8277b139d7d --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-badfilename.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-badfilterparam.xml b/log4j-core-test/src/test/resources/bad/log4j-badfilterparam.xml new file mode 100644 index 00000000000..dbe7fe3663c --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-badfilterparam.xml @@ -0,0 +1,57 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-badlayout.xml b/log4j-core-test/src/test/resources/bad/log4j-badlayout.xml new file mode 100644 index 00000000000..8ca31fd30d6 --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-badlayout.xml @@ -0,0 +1,54 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-loggers.xml b/log4j-core-test/src/test/resources/bad/log4j-loggers.xml new file mode 100644 index 00000000000..acab8f7a3b8 --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-loggers.xml @@ -0,0 +1,55 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + > + + + + > + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-nofilter.xml b/log4j-core-test/src/test/resources/bad/log4j-nofilter.xml new file mode 100644 index 00000000000..4e6ccfc652f --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-nofilter.xml @@ -0,0 +1,57 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/bad/log4j-status.xml b/log4j-core-test/src/test/resources/bad/log4j-status.xml new file mode 100644 index 00000000000..3fdc7487cda --- /dev/null +++ b/log4j-core-test/src/test/resources/bad/log4j-status.xml @@ -0,0 +1,57 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml new file mode 100644 index 00000000000..54c7f846f3f --- /dev/null +++ b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/config/MonitorResource/log4j.json b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.json new file mode 100644 index 00000000000..26c566af41b --- /dev/null +++ b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.json @@ -0,0 +1,15 @@ +{ + "Configuration": { + "monitorInterval": "30", + "MonitorResources": { + "MonitorResource": [ + { + "uri": "file://path/to/external-file-1.txt" + }, + { + "uri": "file://path/to/external-file-2.txt" + } + ] + } + } +} diff --git a/log4j-core-test/src/test/resources/config/MonitorResource/log4j.properties b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.properties new file mode 100644 index 00000000000..dd772d9ff10 --- /dev/null +++ b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.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. +# +monitorInterval = 30 +monitorResources.type = MonitorResources +monitorResources.0.type = MonitorResource +monitorResources.0.uri = file://path/to/external-file-1.txt +monitorResources.1.type = MonitorResource +monitorResources.1.uri = file://path/to/external-file-2.txt diff --git a/log4j-core-test/src/test/resources/config/MonitorResource/log4j.xml b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.xml new file mode 100644 index 00000000000..1529167d56f --- /dev/null +++ b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/log4j-core-test/src/test/resources/config/MonitorResource/log4j.yaml b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.yaml new file mode 100644 index 00000000000..11f804108b7 --- /dev/null +++ b/log4j-core-test/src/test/resources/config/MonitorResource/log4j.yaml @@ -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. +# +Configuration: + monitorInterval: '30' + MonitorResources: + MonitorResource: + - uri: "file://path/to/external-file-1.txt" + - uri: "file://path/to/external-file-2.txt" diff --git a/log4j-core-test/src/test/resources/configPropertyTest.xml b/log4j-core-test/src/test/resources/configPropertyTest.xml new file mode 100644 index 00000000000..baa1b8f4096 --- /dev/null +++ b/log4j-core-test/src/test/resources/configPropertyTest.xml @@ -0,0 +1,47 @@ + + + + + + + + elementValue + + + elementValue3 + + + + + + + + + + + + + elementValue + + + elementValue3 + + ${lower:ELEMENT} + + + diff --git a/log4j-core-test/src/test/resources/csvParamsMixedAsync.xml b/log4j-core-test/src/test/resources/csvParamsMixedAsync.xml new file mode 100644 index 00000000000..f964100f846 --- /dev/null +++ b/log4j-core-test/src/test/resources/csvParamsMixedAsync.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/csvParamsSync.xml b/log4j-core-test/src/test/resources/csvParamsSync.xml similarity index 81% rename from log4j-core/src/test/resources/csvParamsSync.xml rename to log4j-core-test/src/test/resources/csvParamsSync.xml index c2a92c3054f..866c30164e8 100644 --- a/log4j-core/src/test/resources/csvParamsSync.xml +++ b/log4j-core-test/src/test/resources/csvParamsSync.xml @@ -1,11 +1,11 @@ + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml new file mode 100644 index 00000000000..34eb059c4e0 --- /dev/null +++ b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/gcFreeLogging.xml b/log4j-core-test/src/test/resources/gcFreeLogging.xml similarity index 75% rename from log4j-core/src/test/resources/gcFreeLogging.xml rename to log4j-core-test/src/test/resources/gcFreeLogging.xml index 380c12844c9..d982c33258a 100644 --- a/log4j-core/src/test/resources/gcFreeLogging.xml +++ b/log4j-core-test/src/test/resources/gcFreeLogging.xml @@ -1,4 +1,20 @@ + - + @@ -18,21 +34,21 @@ - + - + %d{DEFAULT}{UTC} %r %sn %enc{'/} %notEmpty{[%marker]} %markerSimpleName %MAP %maxLen{%marker}{10} %equals{%markerSimpleName}{test}{substitute} %p %c{1.} [%t] %m%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %m%n} - + %d{yyyy-MM-dd'T'HH:mm:ss,SSS}{UTC} %r %sn %markerSimpleName %MAP %maxLen{%marker}{10} %p %c{1.} [%t] %m%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %m%n} @@ -41,7 +57,7 @@ - + %d{yyyy-MM-dd'T'HH:mm:ss.SSS}{UTC} %r %sn %markerSimpleName %MAP %maxLen{%marker}{10} %p %c{1.} [%t] %X{aKey} %m %ex%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %X{aKey} %m %ex%n} @@ -62,7 +78,6 @@ immediateFlush="false" append="false"> %d{DEFAULT}{UTC} %p %c{1.} [%t] %X{aKey} %m%ex%n - %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %X{aKey} %m%ex%n} diff --git a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml b/log4j-core-test/src/test/resources/gcFreeMixedSyncAsyncLogging.xml similarity index 77% rename from log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml rename to log4j-core-test/src/test/resources/gcFreeMixedSyncAsyncLogging.xml index 91de0a6c2a4..a781cc9d0bb 100644 --- a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml +++ b/log4j-core-test/src/test/resources/gcFreeMixedSyncAsyncLogging.xml @@ -1,4 +1,20 @@ + - + @@ -18,21 +34,21 @@ - + - + %d{yyyy-MM-dd'T'HH:mm:ss,SSS}{UTC} %r %enc{'/} %notEmpty{[%marker]} %sn %markerSimpleName %MAP %maxLen{%marker}{10} %equals{%markerSimpleName}{test}{substitute} %p %c{1.} [%t] %m%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %m%n} - + %d{yyyy-MM-dd'T'HH:mm:ss.SSS}{UTC} %r %sn %markerSimpleName %MAP %maxLen{%marker}{10} %p %c{1.} [%t] %m%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %m%n} @@ -41,7 +57,7 @@ - + %d{DEFAULT}{UTC} %r %sn %markerSimpleName %MAP %maxLen{%marker}{10} %p %c{1.} [%t] %X{aKey} %m %ex%n %highlight{%style{%d}{bright,cyan} %p %c{1.} [%t] %X{aKey} %m %ex%n} diff --git a/log4j-core-test/src/test/resources/log4j+config+with+plus+characters.xml b/log4j-core-test/src/test/resources/log4j+config+with+plus+characters.xml new file mode 100644 index 00000000000..5ec53a0fbbc --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j+config+with+plus+characters.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-Level.xml b/log4j-core-test/src/test/resources/log4j-Level.xml new file mode 100644 index 00000000000..cebcdc97a57 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-Level.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-advertiser.xml b/log4j-core-test/src/test/resources/log4j-advertiser.xml new file mode 100644 index 00000000000..674f2ac0a46 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-advertiser.xml @@ -0,0 +1,53 @@ + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-appender-selector-groovy.xml b/log4j-core-test/src/test/resources/log4j-appender-selector-groovy.xml new file mode 100644 index 00000000000..7e11394612f --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-appender-selector-groovy.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-appender-selector-javascript.xml b/log4j-core-test/src/test/resources/log4j-appender-selector-javascript.xml new file mode 100644 index 00000000000..8cebeb59e47 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-appender-selector-javascript.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-asynch-location.xml b/log4j-core-test/src/test/resources/log4j-asynch-location.xml new file mode 100644 index 00000000000..9da5c4702ff --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-asynch-location.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-asynch-no-location.xml b/log4j-core-test/src/test/resources/log4j-asynch-no-location.xml new file mode 100644 index 00000000000..ef2395e94f6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-asynch-no-location.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-asynch-queue-full.xml b/log4j-core-test/src/test/resources/log4j-asynch-queue-full.xml new file mode 100644 index 00000000000..d95a7801f68 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-asynch-queue-full.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-asynch-shutdownTimeout.xml b/log4j-core-test/src/test/resources/log4j-asynch-shutdownTimeout.xml new file mode 100644 index 00000000000..a0edc458e11 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-asynch-shutdownTimeout.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-asynch.xml b/log4j-core-test/src/test/resources/log4j-asynch.xml new file mode 100644 index 00000000000..38c7c737007 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-asynch.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-burst.xml b/log4j-core-test/src/test/resources/log4j-burst.xml new file mode 100644 index 00000000000..58d355d9ebb --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-burst.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-collectionLogging.xml b/log4j-core-test/src/test/resources/log4j-collectionLogging.xml new file mode 100644 index 00000000000..144e25c1aeb --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-collectionLogging.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 100% rename from log4j-core/src/test/resources/log4j-comp-appender.json rename to log4j-core-test/src/test/resources/log4j-comp-appender.json 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..62bd49f990d --- /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 100% rename from log4j-core/src/test/resources/log4j-comp-filter.json rename to log4j-core-test/src/test/resources/log4j-comp-filter.json 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..00f14fb7f56 --- /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 100% 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 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 96% 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..f2ab8288813 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,18 @@ -{ - "Configuration" : { - "name": "LoggerConfigTest", - "Loggers" : { - "logger" : [ - { - "name" : "cat1", - "level" : "debug", - "additivity" : false, - "AppenderRef" : { - "ref" : "STDOUT", - "RegexFilter": {"regex" : "TEST", "onMatch": "deny", "onMismatch": "accept"} - } - } - ] - } - } -} +{ + "Configuration" : { + "name": "LoggerConfigTest", + "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..2a574889aab --- /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..9288a993c35 --- /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 100% rename from log4j-core/src/test/resources/log4j-comp-logger.json rename to log4j-core-test/src/test/resources/log4j-comp-logger.json 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..a5e6c4e4918 --- /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 100% rename from log4j-core/src/test/resources/log4j-comp-properties.json rename to log4j-core-test/src/test/resources/log4j-comp-properties.json diff --git a/log4j-core-test/src/test/resources/log4j-comp-properties.xml b/log4j-core-test/src/test/resources/log4j-comp-properties.xml new file mode 100644 index 00000000000..4edc0dd91df --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-properties.xml @@ -0,0 +1,33 @@ + + + + + xml + 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 85% 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..e5fa3728f0f 100644 --- a/log4j-core/src/test/resources/log4j-comp-reconfig.properties +++ b/log4j-core-test/src/test/resources/log4j-comp-reconfig.properties @@ -1,18 +1,18 @@ # # 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 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..3ee8ea68e65 --- /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..baf166e8f37 --- /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 100% rename from log4j-core/src/test/resources/log4j-config.json rename to log4j-core-test/src/test/resources/log4j-config.json 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..cea69de857f --- /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..d6c199af028 --- /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..a7ad3e0e552 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-cronRolloverApp.xml @@ -0,0 +1,40 @@ + + + + + target/testlog4 + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-customLevel.xml b/log4j-core-test/src/test/resources/log4j-customLevel.xml new file mode 100644 index 00000000000..db33e9a0ab4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-customLevel.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..bfaf530c7d7 --- /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..fceb11f45ee --- /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..84a578dbc33 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-customLevelsWithFilters.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-cvs-json-parameter.xml b/log4j-core-test/src/test/resources/log4j-cvs-json-parameter.xml new file mode 100644 index 00000000000..d57dbe441b9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-cvs-json-parameter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-date.xml b/log4j-core-test/src/test/resources/log4j-date.xml new file mode 100644 index 00000000000..f709474a218 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-date.xml @@ -0,0 +1,41 @@ + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-deadlock.xml b/log4j-core-test/src/test/resources/log4j-deadlock.xml new file mode 100644 index 00000000000..26a0ea7a450 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-deadlock.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + 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..04a2d0c1954 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-empty.xml @@ -0,0 +1,19 @@ + + + + diff --git a/log4j-core-test/src/test/resources/log4j-failover-location.xml b/log4j-core-test/src/test/resources/log4j-failover-location.xml new file mode 100644 index 00000000000..cff9434582a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-failover-location.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-failover.xml b/log4j-core-test/src/test/resources/log4j-failover.xml new file mode 100644 index 00000000000..47939ed91b9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-failover.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-fatalOnly.xml b/log4j-core-test/src/test/resources/log4j-fatalOnly.xml new file mode 100644 index 00000000000..b3e386f33e4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-fatalOnly.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..9ccc2701576 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-jira965.xml @@ -0,0 +1,31 @@ + + + + + + + %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..46915784a51 --- /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.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..866c30164e8 100644 --- a/log4j-core/src/test/resources/log4j-list.xml +++ b/log4j-core-test/src/test/resources/log4j-list.xml @@ -1,11 +1,11 @@ + + + + + + + + + + + + + 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..77ee43adfd4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-lookup.xml @@ -0,0 +1,32 @@ + + + + + org.junit,org.apache.maven,org.eclipse,sun.reflect,java.lang.reflect + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-marker-lookup.yaml b/log4j-core-test/src/test/resources/log4j-marker-lookup.yaml new file mode 100644 index 00000000000..d79b8d94257 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-marker-lookup.yaml @@ -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. +# +Configuration: + status: warn + + Appenders: + Console: + RandomAccessFile: + - name: SQL_APPENDER + fileName: target/logs/sql.log + append: false + PatternLayout: + Pattern: "%d{ISO8601_BASIC} %-5level %logger{1} %X %msg%n" + - name: PAYLOAD_APPENDER + fileName: target/logs/payload.log + append: false + PatternLayout: + Pattern: "%d{ISO8601_BASIC} %-5level %logger{1} %X %msg%n" + - name: PERFORMANCE_APPENDER + fileName: target/logs/performance.log + append: false + 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 diff --git a/log4j-core-test/src/test/resources/log4j-message-ansi.xml b/log4j-core-test/src/test/resources/log4j-message-ansi.xml new file mode 100644 index 00000000000..45f7522aece --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-message-ansi.xml @@ -0,0 +1,33 @@ + + + + + + + %message{ansi}%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-message-styled.xml b/log4j-core-test/src/test/resources/log4j-message-styled.xml new file mode 100644 index 00000000000..c350732665e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-message-styled.xml @@ -0,0 +1,33 @@ + + + + + + + %message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n + + + + + + + + + + + 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..4ac818038e7 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-nested-logging-throwable-message.xml @@ -0,0 +1,42 @@ + + + + + + + + %level %logger{1} %m %throwable{short.message}%n + + + + + %level %logger{1} %m %throwable{short.message}%n + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-patternSelector.xml b/log4j-core-test/src/test/resources/log4j-patternSelector.xml similarity index 93% rename from log4j-core/src/test/resources/log4j-patternSelector.xml rename to log4j-core-test/src/test/resources/log4j-patternSelector.xml index 76bc8bdbe99..2e464fca02d 100644 --- a/log4j-core/src/test/resources/log4j-patternSelector.xml +++ b/log4j-core-test/src/test/resources/log4j-patternSelector.xml @@ -1,11 +1,11 @@ + + + + + + + + + + + + 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 100% rename from log4j-core/src/test/resources/log4j-reference-level.json rename to log4j-core-test/src/test/resources/log4j-reference-level.json diff --git a/log4j-core-test/src/test/resources/log4j-reference-level.xml b/log4j-core-test/src/test/resources/log4j-reference-level.xml new file mode 100644 index 00000000000..6de521cbde0 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-reference-level.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + > + + + + + > + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-replace.xml b/log4j-core-test/src/test/resources/log4j-replace.xml new file mode 100644 index 00000000000..a684246d76a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-replace.xml @@ -0,0 +1,42 @@ + + + + + + + + %logger %msg{lookups}%n + + + + + %replace{%logger %C{1.} %msg{lookups}%n}{\.}{/} + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rewrite.xml b/log4j-core-test/src/test/resources/log4j-rewrite.xml new file mode 100644 index 00000000000..3287820c1b6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rewrite.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + $${sys:user.name} + ${sys:os.name} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rollOnStartup.json b/log4j-core-test/src/test/resources/log4j-rollOnStartup.json new file mode 100644 index 00000000000..5d158b78a8e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rollOnStartup.json @@ -0,0 +1,41 @@ +{ + "configuration": { "status": "warn", + "appenders": { + "Console": { + "name": "Console", + "PatternLayout": { + "pattern": "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%c{2}] [%t]%n[%p] : %m%n" + } + }, + "RollingFile": { + "name": "RollingFile", + "fileName": "target/rollOnStartup/orchestrator.log", + "filePattern": "target/RollOnStartup/orchestrator-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz", + "append": false, + "PatternLayout": { + "pattern": "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} [%c{2}] [%t]%n[%p] : %m%n" + }, + "Policies": { + "OnStartupTriggeringPolicy": { + "minSize" : 0 + }, + "SizeBasedTriggeringPolicy": { + "size": "50 MB" + } + }, + "DefaultRolloverStrategy": { + "max": "10" + } + } + }, + "loggers": { + "root": { + "level": "info", + "AppenderRef": [ + {"ref": "RollingFile", "level": "INFO"}, + {"ref": "Console", "level": "ERROR"} + ] + } + } + } +} \ No newline at end of file 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..591df3981c9 --- /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..84ade6b576c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-3490.xml @@ -0,0 +1,35 @@ + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-7z-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-7z-lazy.xml new file mode 100644 index 00000000000..9c7800d218e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-7z-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-7z.xml b/log4j-core-test/src/test/resources/log4j-rolling-7z.xml new file mode 100644 index 00000000000..d2ab9c4a10d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-7z.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-bzip2-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-bzip2-lazy.xml new file mode 100644 index 00000000000..8cfc21b5a9e --- /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..b6523fa0c33 --- /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..c64fb47216e --- /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-lookup.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-and-size-lookup.xml new file mode 100644 index 00000000000..c29d9cb8a84 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-and-size-lookup.xml @@ -0,0 +1,55 @@ + + + + + target/rolling1/rollingtest.log + target/rolling-cron-size-lookup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..674ab21a36d --- /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..e94b62c0af2 --- /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..ac1b4cdffde --- /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..a58a9868b95 --- /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..59b2e6617fb --- /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..f2ed5021405 --- /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..fa28a69d407 --- /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-direct-1906.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-1906.xml new file mode 100644 index 00000000000..41ab5bcf9d7 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-1906.xml @@ -0,0 +1,36 @@ + + + + + target/rolling-direct-1906 + + + + + + + + + + + + + + + + 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..bfcfba2a58e --- /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..41d3e83ceac --- /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-with-custom-delete.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-with-custom-delete.xml new file mode 100644 index 00000000000..7066b66f0a4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-with-custom-delete.xml @@ -0,0 +1,45 @@ + + + + + target/rolling-direct-with-delete/ + + + + + + %d %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..b3694f5e7a4 --- /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-posix.xml b/log4j-core-test/src/test/resources/log4j-rolling-gz-posix.xml new file mode 100644 index 00000000000..a797fff3d01 --- /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-new-directory.xml b/log4j-core-test/src/test/resources/log4j-rolling-new-directory.xml new file mode 100644 index 00000000000..01b5283465a --- /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-random-direct-switch-director.xml b/log4j-core-test/src/test/resources/log4j-rolling-random-direct-switch-director.xml new file mode 100644 index 00000000000..638b4381678 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-random-direct-switch-director.xml @@ -0,0 +1,48 @@ + + + + + start log4j-rolling-random-direct-switch-director test + %d %p %C{1.} [%t] %m%n + target/rolling-random-direct-switch-director + + + + + ${LOG_PATTERN} + + + + + ${LOG_PATTERN} + + + + + + + + + + + > + + + + + 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..f3d9bd02a60 --- /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-restart.xml b/log4j-core-test/src/test/resources/log4j-rolling-restart.xml new file mode 100644 index 00000000000..d8cb2b35fc8 --- /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..b4f8dfa6ea9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-1.xml @@ -0,0 +1,48 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + 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..64600014d9d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-2.xml @@ -0,0 +1,48 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + 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..44e6bbc040d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-3.xml @@ -0,0 +1,48 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + 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..54760af6c63 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-4.xml @@ -0,0 +1,52 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + + + + + 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..11418000d8e --- /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..c6a411c8732 --- /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-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..63e955d2fb9 --- /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..44c295035f2 --- /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..4130d8fd822 --- /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..204448980a9 --- /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..d70ab15c809 --- /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-script-fri13th.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-script-fri13th.xml new file mode 100644 index 00000000000..178fb80bab9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-script-fri13th.xml @@ -0,0 +1,75 @@ + + + + + target/rolling-with-delete-script-fri13th/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-script.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-script.xml new file mode 100644 index 00000000000..1f60d96566e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-script.xml @@ -0,0 +1,75 @@ + + + + + target/rolling-with-delete-script/ + + + + + + %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..07a65f3241a --- /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..64c9a9d3570 --- /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..989b863954c --- /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..61f343da311 --- /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/src/test/resources/log4j-rolling.properties b/log4j-core-test/src/test/resources/log4j-rolling.properties similarity index 95% rename from log4j-core/src/test/resources/log4j-rolling.properties rename to log4j-core-test/src/test/resources/log4j-rolling.properties index d02e0a89906..04ec5bb9f30 100644 --- a/log4j-core/src/test/resources/log4j-rolling.properties +++ b/log4j-core-test/src/test/resources/log4j-rolling.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = error name = PropertiesConfigTest @@ -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-test/src/test/resources/log4j-rolling2.xml b/log4j-core-test/src/test/resources/log4j-rolling2.xml new file mode 100644 index 00000000000..c810e0bc960 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling2.xml @@ -0,0 +1,53 @@ + + + + + target/rolling2/rollingtest.log + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + > + + + + + + + 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..bc60e8b148d --- /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..63ada220340 --- /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-test/src/test/resources/log4j-rolling4.xml b/log4j-core-test/src/test/resources/log4j-rolling4.xml new file mode 100644 index 00000000000..b9b6c735573 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling4.xml @@ -0,0 +1,57 @@ + + + + + target/rolling4/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + > + + + + > + + + + + + + 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..4254e91fa64 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-2767.xml @@ -0,0 +1,52 @@ + + + + + target/routing1/routingtest-$${sd:type}.log + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-by-jndi.xml b/log4j-core-test/src/test/resources/log4j-routing-by-jndi.xml new file mode 100644 index 00000000000..27911bcc0ec --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-by-jndi.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-default-route-script-groovy.xml b/log4j-core-test/src/test/resources/log4j-routing-default-route-script-groovy.xml new file mode 100644 index 00000000000..7b9f1d3991e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-default-route-script-groovy.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-default-route-script-javascript.xml b/log4j-core-test/src/test/resources/log4j-routing-default-route-script-javascript.xml new file mode 100644 index 00000000000..f6bc47d30c9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-default-route-script-javascript.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + 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..44a413f5b5b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-lookup.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-purge.xml b/log4j-core-test/src/test/resources/log4j-routing-purge.xml new file mode 100644 index 00000000000..e00e5727dfb --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-purge.xml @@ -0,0 +1,84 @@ + + + + + target/routing-purge-idle/routingtest-$${sd:id}.log + target/routing-purge-manual/routingtest-$${sd:id}.log + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-routes-script-groovy.xml b/log4j-core-test/src/test/resources/log4j-routing-routes-script-groovy.xml new file mode 100644 index 00000000000..68d2aad8836 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-routes-script-groovy.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-routes-script-javascript.xml b/log4j-core-test/src/test/resources/log4j-routing-routes-script-javascript.xml new file mode 100644 index 00000000000..4bcd1f6957a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-routes-script-javascript.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-groovy.xml b/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-groovy.xml new file mode 100644 index 00000000000..bf5f4f689c0 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-groovy.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-javascript.xml b/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-javascript.xml new file mode 100644 index 00000000000..8ce50b1e3fd --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-script-staticvars-javascript.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-routing.json b/log4j-core-test/src/test/resources/log4j-routing.json similarity index 93% rename from log4j-core/src/test/resources/log4j-routing.json rename to log4j-core-test/src/test/resources/log4j-routing.json index 809b3c22783..f6f97d4aa2b 100644 --- a/log4j-core/src/test/resources/log4j-routing.json +++ b/log4j-core-test/src/test/resources/log4j-routing.json @@ -15,9 +15,9 @@ * limitations under the license. */ { "configuration": { "status": "error", "name": "RoutingTest", - "properties": { - "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } - }, + "properties": { + "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } + }, "ThresholdFilter": { "level": "debug" }, "appenders": { "Console": { "name": "STDOUT", diff --git a/log4j-core/src/test/resources/log4j-routing.properties b/log4j-core-test/src/test/resources/log4j-routing.properties similarity index 97% rename from log4j-core/src/test/resources/log4j-routing.properties rename to log4j-core-test/src/test/resources/log4j-routing.properties index c365e35762f..0cfabd61f36 100644 --- a/log4j-core/src/test/resources/log4j-routing.properties +++ b/log4j-core-test/src/test/resources/log4j-routing.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = error name = RoutingTest diff --git a/log4j-core-test/src/test/resources/log4j-routing.xml b/log4j-core-test/src/test/resources/log4j-routing.xml new file mode 100644 index 00000000000..d7d5da3ef1c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing.xml @@ -0,0 +1,58 @@ + + + + + target/routing1/routingtest-$${sd:type}.log + + + + + + + + + + + + + + + + %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 94% rename from log4j-core/src/test/resources/log4j-routing2.json rename to log4j-core-test/src/test/resources/log4j-routing2.json index fe50b37753c..baf475ad077 100644 --- a/log4j-core/src/test/resources/log4j-routing2.json +++ b/log4j-core-test/src/test/resources/log4j-routing2.json @@ -15,9 +15,9 @@ * limitations under the license. */ { "configuration": { "status": "error", "name": "RoutingTest", - "properties": { - "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } - }, + "properties": { + "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } + }, "ThresholdFilter": { "level": "debug" }, "appenders": { "appender": [ diff --git a/log4j-core-test/src/test/resources/log4j-routing3.xml b/log4j-core-test/src/test/resources/log4j-routing3.xml new file mode 100644 index 00000000000..51e5f4f79a3 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing3.xml @@ -0,0 +1,57 @@ + + + + + target/routing1/routingtest.log + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing3350.xml b/log4j-core-test/src/test/resources/log4j-routing3350.xml new file mode 100644 index 00000000000..4acda5cc966 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing3350.xml @@ -0,0 +1,52 @@ + + + + + def + target + /tmp/ + test.log + ${drive}${path}${name} + ${filename}.%i.backup + + + + + + + + %map{data}%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-script-filters.xml b/log4j-core-test/src/test/resources/log4j-script-filters.xml similarity index 84% rename from log4j-core/src/test/resources/log4j-script-filters.xml rename to log4j-core-test/src/test/resources/log4j-script-filters.xml index 85d81d15ea8..12e88c8bdc6 100644 --- a/log4j-core/src/test/resources/log4j-script-filters.xml +++ b/log4j-core-test/src/test/resources/log4j-script-filters.xml @@ -1,11 +1,11 @@ + + + + + + + + + + + + > + + + + + + + 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 86% rename from log4j-core/src/test/resources/log4j-socket-options.xml rename to log4j-core-test/src/test/resources/log4j-socket-options.xml index c665e058a66..3345c77f7ab 100644 --- a/log4j-core/src/test/resources/log4j-socket-options.xml +++ b/log4j-core-test/src/test/resources/log4j-socket-options.xml @@ -1,11 +1,11 @@ + + + target/test.log + + + + + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-style.xml b/log4j-core-test/src/test/resources/log4j-style.xml new file mode 100644 index 00000000000..7c691521c90 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-style.xml @@ -0,0 +1,33 @@ + + + + + + + %d %highlight{%p} %style{%logger}{bright,cyan}%style{}{bright,cyan} %C{1.} %msg%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-test-shutdownTimeout.xml b/log4j-core-test/src/test/resources/log4j-test-shutdownTimeout.xml new file mode 100644 index 00000000000..c745c89b442 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test-shutdownTimeout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-test1.json b/log4j-core-test/src/test/resources/log4j-test1.json similarity index 96% rename from log4j-core/src/test/resources/log4j-test1.json rename to log4j-core-test/src/test/resources/log4j-test1.json index 38a0b22847a..4fe787d9f1c 100644 --- a/log4j-core/src/test/resources/log4j-test1.json +++ b/log4j-core-test/src/test/resources/log4j-test1.json @@ -6,7 +6,7 @@ "properties" : { "property" : { "name" : "filename", - "value": "target/test-json.log" + "value": "${test:logging.path}/test-json.log" } }, "thresholdFilter" : { diff --git a/log4j-core/src/test/resources/log4j-test1.properties b/log4j-core-test/src/test/resources/log4j-test1.properties similarity index 82% rename from log4j-core/src/test/resources/log4j-test1.properties rename to log4j-core-test/src/test/resources/log4j-test1.properties index 3ded2306040..55b79f580a2 100644 --- a/log4j-core/src/test/resources/log4j-test1.properties +++ b/log4j-core-test/src/test/resources/log4j-test1.properties @@ -1,24 +1,24 @@ # # 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 = off name = PropertiesConfigTest -property.filename = target/test-properties.log +property.filename = ${test:logging.path}/test-properties.log filter.threshold.type = ThresholdFilter filter.threshold.level = debug 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..8233fbf38bb --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test1.xml @@ -0,0 +1,57 @@ + + + + + ${test:logging.path}/test-xml.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-test1.yaml b/log4j-core-test/src/test/resources/log4j-test1.yaml new file mode 100644 index 00000000000..cac6703814a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test1.yaml @@ -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. +# +Configuration: + status: warn + name: YAMLConfigTest + properties: + property: + name: filename + value: ${test:logging.path}/test-yaml.log + thresholdFilter: + level: debug + appenders: + Console: + name: STDOUT + PatternLayout: + Pattern: "%m%n" + File: + name: File + fileName: ${filename} + bufferedIO: false + PatternLayout: + Pattern: "%d %p %C{1.} [%t] %m%n" + List: + name: List + Filters: + ThresholdFilter: + level: error + + Loggers: + logger: + - + name: org.apache.logging.log4j.test1 + level: debug + additivity: false + ThreadContextMapFilter: + KeyValuePair: + key: test + value: 123 + AppenderRef: + ref: STDOUT + - + name: org.apache.logging.log4j.test2 + level: debug + additivity: false + AppenderRef: + ref: File + Root: + level: error + AppenderRef: + ref: STDOUT diff --git a/log4j-core/src/test/resources/log4j-test2.properties b/log4j-core-test/src/test/resources/log4j-test2.properties similarity index 85% rename from log4j-core/src/test/resources/log4j-test2.properties rename to log4j-core-test/src/test/resources/log4j-test2.properties index 25bd5dd62ec..d463dd69688 100644 --- a/log4j-core/src/test/resources/log4j-test2.properties +++ b/log4j-core-test/src/test/resources/log4j-test2.properties @@ -1,18 +1,18 @@ # # 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 = debug diff --git a/log4j-core-test/src/test/resources/log4j-test2.xml b/log4j-core-test/src/test/resources/log4j-test2.xml new file mode 100644 index 00000000000..3300d56400b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test2.xml @@ -0,0 +1,93 @@ + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-test3.xml b/log4j-core-test/src/test/resources/log4j-test3.xml new file mode 100644 index 00000000000..cef870f34b1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test3.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + > + + + + + + + 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..9c46d297b64 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test5.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-threaded.xml b/log4j-core-test/src/test/resources/log4j-threaded.xml new file mode 100644 index 00000000000..a61b79dd50b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-threaded.xml @@ -0,0 +1,42 @@ + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-xinclude-appenders.xml b/log4j-core-test/src/test/resources/log4j-xinclude-appenders.xml new file mode 100644 index 00000000000..13550d15278 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-xinclude-appenders.xml @@ -0,0 +1,32 @@ + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-xinclude-loggers.xml b/log4j-core-test/src/test/resources/log4j-xinclude-loggers.xml new file mode 100644 index 00000000000..933f251a71d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-xinclude-loggers.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-xinclude.xml b/log4j-core-test/src/test/resources/log4j-xinclude.xml new file mode 100644 index 00000000000..5816f474ec1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-xinclude.xml @@ -0,0 +1,26 @@ + + + + + ${test:logging.path}/test-xinclude.log + + + + + 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-test/src/test/resources/log4j12-perf.xml b/log4j-core-test/src/test/resources/log4j12-perf.xml new file mode 100644 index 00000000000..da7325392a9 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j12-perf.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-1002.xml b/log4j-core-test/src/test/resources/log4j2-1002.xml new file mode 100644 index 00000000000..6a50ff63f21 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-1002.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + 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..af7af4a622c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-1482.xml @@ -0,0 +1,43 @@ + + + + + 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..4a248118acf --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-1573.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-180.xml b/log4j-core-test/src/test/resources/log4j2-180.xml new file mode 100644 index 00000000000..2c36bbb912e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-180.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-2134.yaml b/log4j-core-test/src/test/resources/log4j2-2134.yaml new file mode 100644 index 00000000000..fe18e5c2b75 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-2134.yaml @@ -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. +# +configuration: + 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-test/src/test/resources/log4j2-272.xml b/log4j-core-test/src/test/resources/log4j2-272.xml new file mode 100644 index 00000000000..35fc79b54c4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-272.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-319.xml b/log4j-core-test/src/test/resources/log4j2-319.xml new file mode 100644 index 00000000000..664353c7f78 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-319.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-744.xml b/log4j-core-test/src/test/resources/log4j2-744.xml new file mode 100644 index 00000000000..aa633146789 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-744.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + 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..29ceff69202 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-962.xml @@ -0,0 +1,34 @@ + + + + + + + + + + %d %m%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-TestConfigurator.xml b/log4j-core-test/src/test/resources/log4j2-TestConfigurator.xml new file mode 100644 index 00000000000..24188d370a1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-TestConfigurator.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + 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..0c350af8257 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-arbiters.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-asyncwaitfactoryconfig-3159-nok.xml b/log4j-core-test/src/test/resources/log4j2-asyncwaitfactoryconfig-3159-nok.xml new file mode 100644 index 00000000000..21512a602bc --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-asyncwaitfactoryconfig-3159-nok.xml @@ -0,0 +1,20 @@ + + + + + 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..a254bb8f221 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-default-suppressed-throwable.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-disableAnsi.xml b/log4j-core-test/src/test/resources/log4j2-console-disableAnsi.xml new file mode 100644 index 00000000000..23fbbe54063 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-disableAnsi.xml @@ -0,0 +1,33 @@ + + + + + + + %d %highlight{%p} %style{%logger}{bright,cyan} %C{1.} %msg%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-highlight-default.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight-default.xml new file mode 100644 index 00000000000..b9ff43a70d6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-highlight-default.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-highlight-logback.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight-logback.xml new file mode 100644 index 00000000000..88b143cff04 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-highlight-logback.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-highlight.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight.xml new file mode 100644 index 00000000000..5c26871fca5 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-highlight.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-noConsoleNoAnsi.xml b/log4j-core-test/src/test/resources/log4j2-console-noConsoleNoAnsi.xml new file mode 100644 index 00000000000..a7933d5af11 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-noConsoleNoAnsi.xml @@ -0,0 +1,33 @@ + + + + + + + %d %highlight{%p} %style{%logger}{bright,cyan} %C{1.} %msg%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-progress.xml b/log4j-core-test/src/test/resources/log4j2-console-progress.xml new file mode 100644 index 00000000000..f864de3c619 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-progress.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-style-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-ansi.xml new file mode 100644 index 00000000000..aaf85d098d2 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-style-ansi.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-style-name-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-name-ansi.xml new file mode 100644 index 00000000000..b1e35d1594b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-style-name-ansi.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-style-no-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-no-ansi.xml new file mode 100644 index 00000000000..4baa5556903 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-style-no-ansi.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml new file mode 100644 index 00000000000..7e8e2c8eedd --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-kirk.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-kirk.xml new file mode 100644 index 00000000000..f8eb11e8973 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-kirk.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml new file mode 100644 index 00000000000..e7b005a9d09 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-console.xml b/log4j-core-test/src/test/resources/log4j2-console.xml new file mode 100644 index 00000000000..d85749123be --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-dynamicfilter.xml b/log4j-core-test/src/test/resources/log4j2-dynamicfilter.xml new file mode 100644 index 00000000000..22d8c70c48d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-dynamicfilter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-environmentArbiters.xml b/log4j-core-test/src/test/resources/log4j2-environmentArbiters.xml new file mode 100644 index 00000000000..6333c93de25 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-environmentArbiters.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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..11ec8c5aab5 --- /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-timestamp.xml b/log4j-core-test/src/test/resources/log4j2-json-layout-timestamp.xml new file mode 100644 index 00000000000..35190a3669f --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-json-layout-timestamp.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..c290142b227 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-json-layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-mapfilter.xml b/log4j-core-test/src/test/resources/log4j2-mapfilter.xml new file mode 100644 index 00000000000..175c7f6484a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-mapfilter.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..09314d317a4 --- /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-test/src/test/resources/log4j2-pattern-layout.xml b/log4j-core-test/src/test/resources/log4j2-pattern-layout.xml new file mode 100644 index 00000000000..cf3af008d75 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-pattern-layout.xml @@ -0,0 +1,33 @@ + + + + + %d{yyyyMMdd HH:mm:ss} %-5p %c{20}:%L - %m%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-perf-filter.xml b/log4j-core-test/src/test/resources/log4j2-perf-filter.xml new file mode 100644 index 00000000000..ce34fd86a77 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-perf-filter.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + %d %5p [%t] %c{1} %X{transactionId} - %m%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-perf.xml b/log4j-core-test/src/test/resources/log4j2-perf.xml new file mode 100644 index 00000000000..c2a1a4e3e8a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-perf.xml @@ -0,0 +1,31 @@ + + + + + + + %d %5p [%t] %c{1} %X{transactionId} - %m%n + + + + + + + + + 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 94% 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..7674b4dc12f 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,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = ERROR 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 94% 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..3a0ec07f713 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,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = ERROR dest = err @@ -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 95% rename from log4j-core/src/test/resources/log4j2-properties.properties rename to log4j-core-test/src/test/resources/log4j2-properties.properties index a6517b74bcc..b2909d0c6fd 100644 --- a/log4j-core/src/test/resources/log4j2-properties.properties +++ b/log4j-core-test/src/test/resources/log4j2-properties.properties @@ -1,7 +1,8 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed 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,6 +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. +# status = ERROR dest = err 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..d3d42350c67 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-random-1833.xml @@ -0,0 +1,40 @@ + + + + + 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..aa70e9ca17d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-rolling-1833.xml @@ -0,0 +1,40 @@ + + + + + target/rolling-1833 + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-script-order-test.xml b/log4j-core-test/src/test/resources/log4j2-script-order-test.xml new file mode 100644 index 00000000000..810b160973b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-script-order-test.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-scriptArbiters.xml b/log4j-core-test/src/test/resources/log4j2-scriptArbiters.xml new file mode 100644 index 00000000000..d13486c399e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-scriptArbiters.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-sdfilter.xml b/log4j-core-test/src/test/resources/log4j2-sdfilter.xml new file mode 100644 index 00000000000..ed7ce1a1fa3 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-sdfilter.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + 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..75396cd4588 --- /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-stringmatchfilter-3153-nok.xml b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-nok.xml new file mode 100644 index 00000000000..8f3755a2201 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-nok.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml new file mode 100644 index 00000000000..1ac8c144af7 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-stringmatchfilter-3153-ok.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-systemPropertyArbiters.xml b/log4j-core-test/src/test/resources/log4j2-systemPropertyArbiters.xml new file mode 100644 index 00000000000..0c350af8257 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-systemPropertyArbiters.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2.StatusLogger.properties b/log4j-core-test/src/test/resources/log4j2.StatusLogger.properties new file mode 100644 index 00000000000..1fa30d7e9d4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2.StatusLogger.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. +# +## +# Adds a timestamp to StatusLogger messages to debug race conditions +log4j2.statusLoggerDateFormat = HH:mm:ss.SSS diff --git a/log4j-core-test/src/test/resources/logback-flume.xml b/log4j-core-test/src/test/resources/logback-flume.xml new file mode 100644 index 00000000000..ebf55544aee --- /dev/null +++ b/log4j-core-test/src/test/resources/logback-flume.xml @@ -0,0 +1,28 @@ + + + + + + %d %5p [%t] %c{0} %X{transactionId} - %m%n + + + + + + + diff --git a/log4j-core-test/src/test/resources/logback-perf-filter.xml b/log4j-core-test/src/test/resources/logback-perf-filter.xml new file mode 100644 index 00000000000..ec00c0bec57 --- /dev/null +++ b/log4j-core-test/src/test/resources/logback-perf-filter.xml @@ -0,0 +1,36 @@ + + + + + + LOG4J + test + + + target/testlogback.log + + %d %5p [%t] %c{0} %X{transactionId} - %m%n + + + + + + + diff --git a/log4j-core-test/src/test/resources/logback-perf.xml b/log4j-core-test/src/test/resources/logback-perf.xml new file mode 100644 index 00000000000..a3db96d8a4e --- /dev/null +++ b/log4j-core-test/src/test/resources/logback-perf.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/logback-test.xml b/log4j-core-test/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..3040c299410 --- /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..20a38a4f5c6 --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/default-level.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 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..b89e1f58179 --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/inherit-level.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 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..c458f440f70 --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/default-level.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 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..804aeb6960c --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/inherit-level.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 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..21d2e512742 --- /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 83% rename from log4j-core/src/test/resources/multipleIncompatibleAppendersTest.xml rename to log4j-core-test/src/test/resources/multipleIncompatibleAppendersTest.xml index f1fd48afcf9..2eeebba77e0 100644 --- a/log4j-core/src/test/resources/multipleIncompatibleAppendersTest.xml +++ b/log4j-core-test/src/test/resources/multipleIncompatibleAppendersTest.xml @@ -1,11 +1,11 @@ - - %d{ISO8601} %r [%t] %-5level %logger{1.} - %msg%n - + + %d{ISO8601} %r [%t] %-5level %logger{1.} - %msg%n + 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..7f1dfbbc279 100644 --- a/log4j-core/src/test/resources/multipleRootLoggersTest.xml +++ b/log4j-core-test/src/test/resources/multipleRootLoggersTest.xml @@ -1,11 +1,11 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source.xml new file mode 100644 index 00000000000..08bacb189e6 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-data-source.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-literal.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-literal.xml new file mode 100644 index 00000000000..07968012f10 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-literal.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-pattern.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-pattern.xml new file mode 100644 index 00000000000..ffe2c99e4d8 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-dm-column-mapping-pattern.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml new file mode 100644 index 00000000000..095a0f8dfdb --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-h2-factory-method.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml new file mode 100644 index 00000000000..0f4653835ba --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-hsqldb-factory-method.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-jdbc-string-substitution.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-jdbc-string-substitution.xml new file mode 100644 index 00000000000..25f41f48ed8 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/db/jdbc/log4j2-jdbc-string-substitution.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.xml new file mode 100644 index 00000000000..4366d96ea8f --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.xml new file mode 100644 index 00000000000..f830c274247 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.xml new file mode 100644 index 00000000000..76a63b03530 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.xml @@ -0,0 +1,37 @@ + + + + + + + %m%n + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.xml new file mode 100644 index 00000000000..e9a9cf79048 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.xml new file mode 100644 index 00000000000..a51a9a092d0 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.xml new file mode 100644 index 00000000000..92c286fceff --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.xml new file mode 100644 index 00000000000..6024e1a9763 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.xml new file mode 100644 index 00000000000..5dc447cc543 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.xml @@ -0,0 +1,39 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.xml new file mode 100644 index 00000000000..71bb09b4965 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAbstractTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAbstractTest.xml new file mode 100644 index 00000000000..095e35ef4aa --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAbstractTest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.xml new file mode 100644 index 00000000000..ab72a308410 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncAppender1Test.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.xml new file mode 100644 index 00000000000..5322b67a1e6 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfig1Test.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/CustomConfigurationTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/CustomConfigurationTest.xml new file mode 100644 index 00000000000..551bb1d78c3 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/CustomConfigurationTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/FileOutputTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/FileOutputTest.xml new file mode 100644 index 00000000000..09ba189b53f --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/FileOutputTest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.xml new file mode 100644 index 00000000000..45e7afa959b --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest1.xml b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest1.xml new file mode 100644 index 00000000000..ac5fdd98457 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest1.xml @@ -0,0 +1,22 @@ + + + + + + + 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-test/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 new file mode 100644 index 00000000000..905d5510f7f --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle.properties @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +KeyA = ValueA diff --git a/log4j-core-test/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 new file mode 100644 index 00000000000..905d5510f7f --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle_en.properties @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to you under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +KeyA = ValueA diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore new file mode 100644 index 00000000000..3fec32c8427 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh new file mode 100755 index 00000000000..df194cabe5a --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/generate-stores.sh @@ -0,0 +1,129 @@ +#!/bin/bash -eu +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Switch to the script directory +cd -- "$(dirname -- "${BASH_SOURCE[0]}")" + +# Reset and switch to the `tmp` +rm -rf tmp +mkdir tmp +cd tmp + +# Constants +caPassword="aCaSecret" +keySize=2048 +validDays=$[365 * 10] + +### CA key and certificate #################################################### + +# Generate the CA configuration +cat >ca.cfg < + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf-CountingNoOpAppender.xml b/log4j-core-test/src/test/resources/perf-CountingNoOpAppender.xml new file mode 100644 index 00000000000..b75e31b720e --- /dev/null +++ b/log4j-core-test/src/test/resources/perf-CountingNoOpAppender.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf-log4j12-async.xml b/log4j-core-test/src/test/resources/perf-log4j12-async.xml new file mode 100644 index 00000000000..65580b9d7d2 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf-log4j12-async.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf-log4j12.xml b/log4j-core-test/src/test/resources/perf-log4j12.xml new file mode 100644 index 00000000000..55dc343c791 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf-log4j12.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf-logback-async.xml b/log4j-core-test/src/test/resources/perf-logback-async.xml new file mode 100644 index 00000000000..ac897e8504b --- /dev/null +++ b/log4j-core-test/src/test/resources/perf-logback-async.xml @@ -0,0 +1,38 @@ + + + + + + perftest.log + false + + %d %p %c{1} [%t] %X{aKey} %m %ex%n + false + + + + 262144 + 0 + false + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf-logback.xml b/log4j-core-test/src/test/resources/perf-logback.xml new file mode 100644 index 00000000000..0f55774555c --- /dev/null +++ b/log4j-core-test/src/test/resources/perf-logback.xml @@ -0,0 +1,32 @@ + + + + + + perftest.log + false + + %d %p %c{1} [%t] %X{aKey} %m %ex%n + false + + + + + + + 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-test/src/test/resources/perf1syncFastFile.xml b/log4j-core-test/src/test/resources/perf1syncFastFile.xml new file mode 100644 index 00000000000..7bc8af6e95e --- /dev/null +++ b/log4j-core-test/src/test/resources/perf1syncFastFile.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf1syncFile.xml b/log4j-core-test/src/test/resources/perf1syncFile.xml new file mode 100644 index 00000000000..c96cc0d9dbc --- /dev/null +++ b/log4j-core-test/src/test/resources/perf1syncFile.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf2syncRollFastFile.xml b/log4j-core-test/src/test/resources/perf2syncRollFastFile.xml new file mode 100644 index 00000000000..7db2efbe0f9 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf2syncRollFastFile.xml @@ -0,0 +1,36 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf2syncRollFile.xml b/log4j-core-test/src/test/resources/perf2syncRollFile.xml new file mode 100644 index 00000000000..234b9e099a8 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf2syncRollFile.xml @@ -0,0 +1,36 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf3PlainNoLoc.xml b/log4j-core-test/src/test/resources/perf3PlainNoLoc.xml new file mode 100644 index 00000000000..95991298c25 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf3PlainNoLoc.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf4PlainLocation.xml b/log4j-core-test/src/test/resources/perf4PlainLocation.xml new file mode 100644 index 00000000000..396031807cb --- /dev/null +++ b/log4j-core-test/src/test/resources/perf4PlainLocation.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf5AsyncApndNoLoc.xml b/log4j-core-test/src/test/resources/perf5AsyncApndNoLoc.xml new file mode 100644 index 00000000000..70700641889 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf5AsyncApndNoLoc.xml @@ -0,0 +1,34 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf6AsyncApndLoc.xml b/log4j-core-test/src/test/resources/perf6AsyncApndLoc.xml new file mode 100644 index 00000000000..0a375843ab9 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf6AsyncApndLoc.xml @@ -0,0 +1,34 @@ + + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf7MixedNoLoc.xml b/log4j-core-test/src/test/resources/perf7MixedNoLoc.xml new file mode 100644 index 00000000000..13b256a4f98 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf7MixedNoLoc.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/perf8MixedLoc.xml b/log4j-core-test/src/test/resources/perf8MixedLoc.xml new file mode 100644 index 00000000000..d876a2fcce0 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf8MixedLoc.xml @@ -0,0 +1,31 @@ + + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + 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..2693e7c8b1c --- /dev/null +++ b/log4j-core-test/src/test/resources/perf9MMapNoLoc.xml @@ -0,0 +1,35 @@ + + + + + + + %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..946a8d6d8ae --- /dev/null +++ b/log4j-core-test/src/test/resources/reconfiguration-deadlock.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + 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..f6b72669434 --- /dev/null +++ b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.original.xml @@ -0,0 +1,55 @@ + + + + + ${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..abfa8acaec8 --- /dev/null +++ b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.xml @@ -0,0 +1,55 @@ + + + + + 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..54cc5675328 --- /dev/null +++ b/log4j-core-test/src/test/resources/rollover-test.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + 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-test/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 new file mode 100644 index 00000000000..5ec53a0fbbc --- /dev/null +++ b/log4j-core-test/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/scripts/filter.groovy b/log4j-core-test/src/test/resources/scripts/filter.groovy similarity index 98% rename from log4j-core/src/test/resources/scripts/filter.groovy rename to log4j-core-test/src/test/resources/scripts/filter.groovy index 2beac2f8dc6..83c23d95243 100644 --- a/log4j-core/src/test/resources/scripts/filter.groovy +++ b/log4j-core-test/src/test/resources/scripts/filter.groovy @@ -1 +1 @@ -return logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextMap.containsKey('UserId') +return logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextMap.containsKey('UserId') diff --git a/log4j-core/src/test/resources/scripts/filter.js b/log4j-core-test/src/test/resources/scripts/filter.js similarity index 96% rename from log4j-core/src/test/resources/scripts/filter.js rename to log4j-core-test/src/test/resources/scripts/filter.js index 3619d5f33b7..e540f1feb40 100644 --- a/log4j-core/src/test/resources/scripts/filter.js +++ b/log4j-core-test/src/test/resources/scripts/filter.js @@ -1,7 +1,7 @@ -var result = false; -if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) { - result = true; -} else if (logEvent.getContextMap().containsKey("UserId")) { - result = true; -} -result; +var result = false; +if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) { + result = true; +} else if (logEvent.getContextMap().containsKey("UserId")) { + result = true; +} +result; 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-test/src/test/resources/xml-events.xml b/log4j-core-test/src/test/resources/xml-events.xml new file mode 100644 index 00000000000..8bc634df685 --- /dev/null +++ b/log4j-core-test/src/test/resources/xml-events.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/.log4j-plugin-processing-activator b/log4j-core/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-core/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index 4ae808ac28b..49293f47dfc 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -1,49 +1,138 @@ + 4.0.0 + org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + ${revision} + ../log4j-parent + log4j-core - jar + Apache Log4j Core - The Apache Log4j Implementation + + A versatile, industrial-grade, and reference implementation of the Log4j API. + It bundles a rich set of components to assist various use cases: + Appenders targeting files, network sockets, databases, SMTP servers; + Layouts that can render CSV, HTML, JSON, Syslog, etc. formatted outputs; + Filters that can be configured using log event rates, regular expressions, scripts, time, etc. + It contains several extension points to introduce custom components, if needed. + - ${basedir}/.. - Core Documentation - /core + + + false + + + true + + + ${log4j.docgen.pluginDescriptorsDir.phase1} + + + [3.4,5) + true + + + org.jspecify.*;resolution:=optional, + + com.conversantmedia.util.concurrent;resolution:=optional; + com.fasterxml.jackson.*;resolution:=optional, + com.lmax.disruptor.*;version="${disruptor.support.range}";resolution:=optional, + javax.activation;resolution:=optional, + javax.jms;version="[1.1,3)";resolution:=optional, + javax.mail.*;version="[1.6,2)";resolution:=optional, + org.apache.commons.compress.*;resolution:=optional, + org.apache.commons.csv;resolution:=optional, + org.apache.kafka.*;resolution:=optional, + org.codehaus.stax2;resolution:=optional, + org.jctools.*;resolution:=optional, + org.zeromq;resolution:=optional, + javax.lang.model.*;resolution:=optional, + javax.tools;resolution:=optional, + + java.sql;resolution:=optional, + javax.sql;resolution:=optional, + java.util.logging;resolution:=optional, + + java.lang.management;resolution:=optional, + javax.management.*;resolution:=optional, + + javax.naming;resolution:=optional + + + + org.osgi.core;static=true;transitive=false, + + java.logging;static=true, + java.sql;static=true, + + com.fasterxml.jackson.annotation;transitive=false, + com.lmax.disruptor;transitive=false, + com.fasterxml.jackson.core;transitive=false, + com.fasterxml.jackson.databind;transitive=false, + com.fasterxml.jackson.dataformat.xml;transitive=false, + com.fasterxml.jackson.dataformat.yaml;transitive=false, + java.management;transitive=false;static=true, + java.naming;transitive=false, + org.apache.commons.csv;transitive=false, + org.jspecify;transitive=false, + org.zeromq.jeromq;transitive=false, + + com.conversantmedia.disruptor;substitute="disruptor";transitive=false;static=true, + + kafka.clients;substitute="kafka-clients";transitive=false;static=true, + javax.jms.api;substitute="javax.jms-api";transitive=false;static=true, + javax.mail.api;substitute="javax.mail-api";transitive=false;static=true + + - - org.apache.logging.log4j - log4j-api + javax.activation + javax.activation-api + provided + true - + - org.apache.logging.log4j - log4j-core-java9 + javax.jms + javax.jms-api + provided + true + + + + javax.mail + javax.mail-api + provided + true + + + org.jspecify + jspecify provided - zip @@ -51,23 +140,33 @@ org.osgi.core provided - + - com.lmax - disruptor + org.apache.logging.log4j + log4j-api + + + + org.apache.commons + commons-compress + true + + + + org.apache.commons + commons-csv true com.conversantmedia disruptor - jdk7 true - + - org.jctools - jctools-core + com.lmax + disruptor true @@ -82,48 +181,22 @@ 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 - true - - - - org.fusesource.jansi - jansi - true - - - - com.sun.mail - javax.mail - true - - + - org.jboss.spec.javax.jms - jboss-jms-api_1.1_spec - provided + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml true - + - org.apache.kafka - kafka-clients + org.jctools + jctools-core true @@ -132,251 +205,34 @@ jeromq true - - - org.apache.commons - commons-compress - true - - + - org.apache.commons - commons-csv + org.apache.kafka + kafka-clients true - - com.beust - jcommander + com.sun.mail + javax.mail + runtime true - - - - - - org.apache.logging.log4j - log4j-api - test-jar - test - - - - org.tukaani - xz - test - - - - 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 - + - - 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 + generate-sources ${project.build.directory}/log4j-core-java9 @@ -385,246 +241,38 @@ - - maven-compiler-plugin - - - - default-compile - - - module-info.java - - none - - - - - process-plugins - - compile - - process-classes - - - module-info.java - - only - - - - - - maven-surefire-plugin - - - org.apache.logging.log4j.categories.PerformanceTests - - 1 - false - - * - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - + org.apache.maven.plugins - maven-jar-plugin + maven-dependency-plugin - default-jar - - 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.logging.log4j.core - true - - - - - - default + unpack-classes - test-jar + unpack + prepare-package - - ${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.logging.log4j + log4j-core-java9 + ${project.version} + zip + false + + + **/*.class + **/*.java + ${project.build.directory} + false + true - - 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 - - - + - - - - org.apache.maven.plugins - maven-changes-plugin - ${changes.plugin.version} - - - - changes-report - - - - - %URL%/show_bug.cgi?id=%ISSUE% - true - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${checkstyle.plugin.version} - - - ${log4jParentDir}/checkstyle.xml - ${log4jParentDir}/checkstyle-suppressions.xml - false - basedir=${basedir} - licensedir=${log4jParentDir}/checkstyle-header.txt - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc.plugin.version} - - false - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
- 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.

]]>
- - 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/ - - - - Core API - org.apache.logging.log4j.core - - - Configuration - org.apache.logging.log4j.core.config*:org.apache.logging.log4j.core.selector - - - Core Plugins - org.apache.logging.log4j.core.appender*:org.apache.logging.log4j.core.filter:org.apache.logging.log4j.core.layout:org.apache.logging.log4j.core.lookup:org.apache.logging.log4j.core.pattern:org.apache.logging.log4j.core.script - - - Tools - org.apache.logging.log4j.core.net*:org.apache.logging.log4j.core.tools - - - Internals - org.apache.logging.log4j.core.async:org.apache.logging.log4j.core.impl:org.apache.logging.log4j.core.util*:org.apache.logging.log4j.core.osgi:org.apache.logging.log4j.core.jackson:org.apache.logging.log4j.core.jmx - - -
- - - non-aggregate - - javadoc - - - -
- - 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 - - jxr - - - - aggregate - - aggregate - - - - - - org.apache.maven.plugins - maven-pmd-plugin - ${pmd.plugin.version} - - ${maven.compiler.target} - - -
-
-
+ 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..fca1ead0949 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 @@ -1,24 +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. */ package org.apache.logging.log4j.core; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.status.StatusLogger; /** @@ -39,7 +38,7 @@ public class AbstractLifeCycle implements LifeCycle2 { /** * Gets the status logger. - * + * * @return the status logger. */ protected static org.apache.logging.log4j.Logger getStatusLogger() { @@ -152,5 +151,4 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { this.state = LifeCycle.State.STOPPED; return true; } - } 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..c79fa60beb1 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 @@ -1,24 +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. */ 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; @@ -29,7 +28,6 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.util.ReadOnlyStringMap; - /** * An abstract log event implementation with default values for all methods. The setters are no-ops. */ @@ -37,7 +35,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. @@ -127,6 +125,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..2be519cfb97 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; @@ -44,6 +44,11 @@ public interface Appender extends LifeCycle { */ 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 * bridge pattern not only for the benefits from decoupling an Appender from its implementation, but it is also @@ -53,7 +58,6 @@ public interface Appender extends LifeCycle { */ void append(LogEvent event); - /** * Gets the name of this Appender. * 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..e61bfe57440 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 @@ -1,23 +1,22 @@ /* * 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.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; @@ -27,6 +26,9 @@ /** * 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. *

@@ -103,6 +105,19 @@ public interface ContextDataInjector { * the implementation of this method. It is not safe to pass the returned object to another thread. *

* @return a {@code ReadOnlyStringMap} object reflecting the current state of the context, may not return {@code null} + * @deprecated Since 2.24.0 use {@link #getValue} instead. */ + @Deprecated ReadOnlyStringMap rawContextData(); + + /** + * Retrieves a single context data value. + * + * @param key The context data key of the value to retrieve. + * @return A context data value. + * @since 2.24.0 + */ + default Object getValue(final String key) { + return rawContextData().getValue(key); + } } 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..95a4eb76a29 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 @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core; public class Core { public static final String CATEGORY_NAME = "Core"; - } 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..8b6cdf9a0ad 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core; /** @@ -29,12 +28,11 @@ public class DefaultLoggerContextAccessor implements LoggerContextAccessor { /* * Returns the current LoggerContext. - * + * * @see org.apache.logging.log4j.core.LoggerContextAccessor#getLoggerContext() */ @Override public LoggerContext getLoggerContext() { return LoggerContext.getContext(); } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ErrorHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ErrorHandler.java index a6c97c14725..81b355498c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/ErrorHandler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ErrorHandler.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; 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..9f2bad55d62 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 @@ -1,25 +1,25 @@ /* * 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.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; /** @@ -32,6 +32,11 @@ */ public interface Filter extends LifeCycle { + /** + * The empty array. + */ + Filter[] EMPTY_ARRAY = {}; + /** * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for * Filter plugins. @@ -43,7 +48,7 @@ public interface Filter extends LifeCycle { /** * The result that can returned from a filter method call. */ - enum Result { + enum Result { /** * The event will be processed without further filtering based on the log Level. */ @@ -72,12 +77,12 @@ public static Result toResult(final String name) { * * @param name The Result enum name, case-insensitive. If null, returns, defaultResult * @param defaultResult the Result to return if name is null - * @return a Result enum value or null if name is null + * @return a Result enum value ({@code defaultResult} if name is null) */ public static Result toResult(final String name, final Result defaultResult) { return EnglishEnums.valueOf(Result.class, name, defaultResult); } -} + } /** * Returns the result that should be returned when the filter does not match the event. @@ -153,7 +158,8 @@ public static Result toResult(final String name, final Result defaultResult) { * @param p3 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3); + Result filter( + Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3); /** * Filter an event. @@ -169,7 +175,15 @@ public static Result toResult(final String name, final Result defaultResult) { * @param p4 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, + Result filter( + Logger logger, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, Object p4); /** @@ -187,8 +201,17 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object * @param p5 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5); + Result filter( + Logger logger, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5); /** * Filter an event. @@ -206,8 +229,18 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object * @param p6 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6); + Result filter( + Logger logger, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6); /** * Filter an event. @@ -226,8 +259,19 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object * @param p7 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6, Object p7); + Result filter( + Logger logger, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7); /** * Filter an event. @@ -247,8 +291,20 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object * @param p8 the message parameters * @return the Result. */ - Result filter(Logger logger, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, - Object p4, Object p5, Object p6, Object p7, Object p8); + Result filter( + Logger logger, + Level level, + Marker marker, + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8); /** * Filter an event. @@ -269,8 +325,21 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object * @param p9 the message parameters * @return the Result. */ - Result filter(Logger logger, 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); + Result filter( + Logger logger, + 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); /** * Filter an event. @@ -294,11 +363,22 @@ 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. * @return the Result. */ Result filter(LogEvent event); - } 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..ad26ecb90ae 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 @@ -1,24 +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. */ package org.apache.logging.log4j.core; import java.io.Serializable; import java.util.Map; - import org.apache.logging.log4j.core.layout.ByteBufferDestination; import org.apache.logging.log4j.core.layout.Encoder; 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..46d64b2b33f 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core; /** @@ -65,5 +64,4 @@ enum State { boolean isStarted(); boolean isStopped(); - } 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 index 0046a62f6e2..2fe54241e36 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; 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..63daa9d3bbe 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 @@ -1,25 +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. */ - 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; import org.apache.logging.log4j.ThreadContext; @@ -51,8 +49,9 @@ 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 + * @since 2.8.1 */ LogEvent toImmutable(); @@ -133,14 +132,14 @@ 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 + * @return the {@code Instant} holding Instant details for this log event * @since 2.11 */ Instant getInstant(); @@ -189,7 +188,9 @@ public interface LogEvent extends Serializable { * Gets throwable proxy associated with logging request. * * @return throwable, may be null. + * @deprecated since 2.25.0. This method should be replaced with {@link #getThrown()}. */ + @Deprecated ThrowableProxy getThrownProxy(); /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEventListener.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEventListener.java index 11287363f93..1580441979a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEventListener.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEventListener.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core; import java.util.EventListener; - import org.apache.logging.log4j.status.StatusLogger; /** 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..fdd2465099f 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 @@ -1,34 +1,39 @@ /* * 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.logging.log4j.core; +import static java.util.Objects.requireNonNull; + 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; - import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LocationAwareReliabilityStrategy; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.ReliabilityStrategy; import org.apache.logging.log4j.core.filter.CompositeFilter; +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.SimpleMessage; @@ -57,20 +62,35 @@ public class Logger extends AbstractLogger implements Supplier { */ protected volatile PrivateConfig privateConfig; - // FIXME: ditto to the above private final LoggerContext context; /** - * The constructor. + * Constructs an instance using the given {@link LoggerContext}, logger name, and {@link MessageFactory}. + * + * @param context The {@link LoggerContext} this logger is associated with, never {@code null}. + * @param name The logger name, never {@code null}. + * @param messageFactory The message factory to be used. + * Passing a {@code null} value is deprecated, but supported for backward compatibility. + */ + protected Logger(LoggerContext context, String name, MessageFactory messageFactory) { + this(context, name, messageFactory, LoggerContext.DEFAULT_FLOW_MESSAGE_FACTORY); + } + + /** + * The canonical constructor. * - * @param context The LoggerContext this Logger is associated with. - * @param messageFactory The message factory. - * @param name The name of the Logger. + * @param context The {@link LoggerContext} this logger is associated with, never {@code null}. + * @param name The logger name, never {@code null}. + * @param messageFactory The message factory to be used. + * Passing a {@code null} value is deprecated, but supported for backward compatibility. + * @param flowMessageFactory The flow message factory to be used. + * Passing a {@code null} value is deprecated, but supported for backward compatibility. */ - protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) { - super(name, messageFactory); - this.context = context; - privateConfig = new PrivateConfig(context.getConfiguration(), this); + protected Logger( + LoggerContext context, String name, MessageFactory messageFactory, FlowMessageFactory flowMessageFactory) { + super(name, messageFactory, flowMessageFactory); + this.context = requireNonNull(context, "context"); + this.privateConfig = new PrivateConfig(context.getConfiguration(), this); } protected Object writeReplace() throws ObjectStreamException { @@ -84,8 +104,9 @@ protected Object writeReplace() throws ObjectStreamException { * @return The parent Logger. */ public Logger getParent() { - final LoggerConfig lc = privateConfig.loggerConfig.getName().equals(getName()) ? privateConfig.loggerConfig - .getParent() : privateConfig.loggerConfig; + final LoggerConfig lc = privateConfig.loggerConfig.getName().equals(getName()) + ? privateConfig.loggerConfig.getParent() + : privateConfig.loggerConfig; if (lc == null) { return null; } @@ -139,13 +160,36 @@ public LoggerConfig get() { } @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, - final Throwable t) { + 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) { final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message; final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); 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(); + if (strategy instanceof LocationAwareReliabilityStrategy) { + ((LocationAwareReliabilityStrategy) strategy) + .log(this, getName(), fqcn, location, marker, level, message, throwable); + } else { + strategy.log(this, getName(), fqcn, 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); @@ -167,65 +211,124 @@ public boolean isEnabled(final Level level, final Marker marker, final String me } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1) { + public boolean isEnabled( + final Level level, final Marker marker, final String message, final Object p0, final Object p1) { return privateConfig.filter(level, marker, message, p0, p1); } @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { return privateConfig.filter(level, marker, message, p0, p1, p2); } @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) { + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3); } @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, + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4); } @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) { + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4, p5); } @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) { + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4, p5, p6); } @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, + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); } @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) { + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); } @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) { + 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 privateConfig.filter(level, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } @@ -280,7 +383,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 { @@ -344,6 +447,16 @@ public void setAdditive(final boolean additive) { privateConfig.config.setLoggerAdditive(this, additive); } + @Override + public LogBuilder atLevel(final Level level) { + // A global filter might accept messages less specific than level. + // Therefore we return always a functional builder. + if (privateConfig.hasFilter()) { + return getLogBuilder(level); + } + return super.atLevel(level); + } + /** * Associates this Logger with a new Configuration. This method is not * exposed through the public API. @@ -374,9 +487,11 @@ protected class PrivateConfig { public final LoggerConfig loggerConfig; // SUPPRESS CHECKSTYLE /** The current Configuration associated with the LoggerConfig. */ public final Configuration config; // SUPPRESS CHECKSTYLE + 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 +499,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 +508,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 +517,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 @@ -407,6 +525,10 @@ public void logEvent(final LogEvent event) { loggerConfig.log(event); } + boolean hasFilter() { + return config.getFilter() != null; + } + boolean filter(final Level level, final Marker marker, final String msg) { final Filter filter = config.getFilter(); if (filter != null) { @@ -451,8 +573,7 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(final Level level, final Marker marker, final String msg, final Object p0, - final Object p1) { + boolean filter(final Level level, final Marker marker, final String msg, final Object p0, final Object p1) { final Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1); @@ -463,8 +584,13 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(final Level level, final Marker marker, final String msg, final Object p0, - final Object p1, final Object p2) { + boolean filter( + final Level level, + final Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2) { final Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2); @@ -475,8 +601,14 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(final Level level, final Marker marker, final String msg, final Object p0, - final Object p1, final Object p2, final Object p3) { + boolean filter( + final Level level, + final Marker marker, + final String msg, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { final Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2, p3); @@ -487,8 +619,14 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(final Level level, final Marker marker, final String msg, final Object p0, - final Object p1, final Object p2, final Object p3, + boolean filter( + 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 Filter filter = config.getFilter(); if (filter != null) { @@ -500,9 +638,16 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(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) { + boolean filter( + 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 Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5); @@ -513,9 +658,17 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(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) { + boolean filter( + 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 Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6); @@ -526,9 +679,17 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(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, + boolean filter( + 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 Filter filter = config.getFilter(); if (filter != null) { @@ -540,10 +701,19 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(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) { + boolean filter( + 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 Filter filter = config.getFilter(); if (filter != null) { final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7, p8); @@ -554,14 +724,24 @@ boolean filter(final Level level, final Marker marker, final String msg, final O return level != null && intLevel >= level.intLevel(); } - boolean filter(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) { + boolean filter( + 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) { final Filter filter = config.getFilter(); if (filter != null) { - final Filter.Result r = filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, - p9); + final Filter.Result r = + filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); if (r != Filter.Result.NEUTRAL) { return r == Filter.Result.ACCEPT; } 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..6db471cb87a 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 @@ -1,35 +1,38 @@ /* * 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.logging.log4j.core; +import static org.apache.logging.log4j.core.jmx.internal.JmxUtil.isJmxDisabled; import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; 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; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; @@ -41,18 +44,24 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.jmx.Server; import org.apache.logging.log4j.core.util.Cancellable; +import org.apache.logging.log4j.core.util.Constants; 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.core.util.internal.InternalLoggerRegistry; +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.spi.AbstractLogger; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.ReusableMessageFactory; import org.apache.logging.log4j.spi.LoggerContextFactory; -import org.apache.logging.log4j.spi.LoggerRegistry; +import org.apache.logging.log4j.spi.LoggerContextShutdownAware; +import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled; 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 org.jspecify.annotations.Nullable; /** * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by @@ -60,33 +69,69 @@ * 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"; + private static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__"; + + private static final String MESSAGE_FACTORY_PROPERTY_NAME = "log4j2.messageFactory"; + /** + * The default message factory to use while creating loggers if the user provides none. + *

+ * To mitigate initialization problems as the one described in + * + * To mitigate initialization problems as the one described in + * propertyChangeListeners = new CopyOnWriteArrayList<>(); + private volatile List listeners; /** * 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 final ConcurrentMap externalMap = new ConcurrentHashMap<>(); private String contextName; private volatile URI configLocation; private Cancellable shutdownCallback; @@ -121,7 +166,9 @@ public LoggerContext(final String name, final Object externalContext) { */ public LoggerContext(final String name, final Object externalContext, final URI configLocn) { this.contextName = name; - this.externalContext = externalContext; + if (externalContext != null) { + externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); + } this.configLocation = configLocn; } @@ -133,9 +180,14 @@ public LoggerContext(final String name, final Object externalContext, final URI * @param externalContext The external context. * @param configLocn The configuration location. */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The configLocn comes from a secure source (Log4j properties)") public LoggerContext(final String name, final Object externalContext, final String configLocn) { this.contextName = name; - this.externalContext = externalContext; + if (externalContext != null) { + externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); + } if (configLocn != null) { URI uri; try { @@ -149,6 +201,23 @@ public LoggerContext(final String name, final Object externalContext, final Stri } } + @Override + public void addShutdownListener(final LoggerContextShutdownAware listener) { + if (listeners == null) { + synchronized (this) { + if (listeners == null) { + listeners = new CopyOnWriteArrayList<>(); + } + } + } + listeners.add(listener); + } + + @Override + public List getListeners() { + return listeners; + } + /** * Returns the current LoggerContext. *

@@ -212,16 +281,27 @@ public static LoggerContext getContext(final boolean currentContext) { * @return a LoggerContext. * @see LogManager#getContext(ClassLoader, boolean, URI) */ - public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, - final URI configLocation) { + public static LoggerContext getContext( + final ClassLoader loader, final boolean currentContext, final URI configLocation) { return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation); } + /** + * Starts the context using the configuration specified by {@link #getConfigLocation()}. + *

+ * If the configuration location is {@code null}, Log4j will search for a configuration file + * using the default classpath resources. For details on the search order and supported formats, + * see the + * + * Log4j 2 Configuration File Location documentation. + *

+ */ @Override public void start() { LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this); if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) { - LOGGER.debug("Stack trace to locate invoker", + LOGGER.debug( + "Stack trace to locate invoker", new Exception("Not a real error, showing stack trace to locate invoker")); } if (configLock.tryLock()) { @@ -242,26 +322,37 @@ public void start() { } /** - * Starts with a specific configuration. - * - * @param config The new Configuration. + * Starts the context using a specific configuration. + *

+ * Warning: For backward compatibility, especially with Spring Boot, + * if the context is already started, this method will fall back to {@link #reconfigure(Configuration)}. + * This behavior is maintained for legacy integrations and may change in future major versions. + * New code should not rely on this fallback. + *

+ * @param config The new {@link Configuration} to use for this context */ public void start(final Configuration config) { - LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config); + LOGGER.info("Starting {}[name={}] with configuration {}...", getClass().getSimpleName(), getName(), config); if (configLock.tryLock()) { try { - if (this.isInitialized() || this.isStopped()) { + if (isInitialized() || isStopped()) { + setStarting(); + reconfigure(config); if (this.configuration.isShutdownHookEnabled()) { setUpShutdownHook(); } - this.setStarted(); + setStarted(); + } else { + // Required for Spring Boot integration: + // Both `Log4jSpringBootLoggingSystem` and its Spring Boot 3.x equivalent + // invoke `start()` even during context reconfiguration. + reconfigure(config); } } finally { configLock.unlock(); } } - setConfiguration(config); - LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config); + LOGGER.info("{}[name={}] started with configuration {}.", getClass().getSimpleName(), getName(), config); } private void setUpShutdownHook() { @@ -269,15 +360,19 @@ 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() { @Override 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 LoggerContext[name={}, {}]", + context.getName(), + context); context.stop(shutdownTimeoutMillis, TimeUnit.MILLISECONDS); } @@ -290,8 +385,8 @@ public String toString() { throw new IllegalStateException( "Unable to register Log4j shutdown hook because JVM is shutting down.", e); } catch (final SecurityException e) { - LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions", - e); + LOGGER.error( + SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions", e); } } } @@ -319,7 +414,7 @@ public void terminate() { * block until the rollover thread is done. * * @param timeout the maximum time to wait, or 0 which mean that each apppender uses its default timeout, and don't wait for background - tasks + * tasks * @param timeUnit * the time unit of the timeout argument * @return {@code true} if the logger context terminated and {@code false} if the timeout elapsed before @@ -336,12 +431,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { } this.setStopping(); - try { - Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 - } catch (final LinkageError | Exception e) { - // LOG4J2-1506 Hello Android, GAE - LOGGER.error("Unable to unregister MBeans", e); - } + unregisterJmxBeans(); if (shutdownCallback != null) { shutdownCallback.cancel(); shutdownCallback = null; @@ -354,16 +444,36 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { } else { prev.stop(); } - externalContext = null; + externalMap.clear(); LogManager.getFactory().removeContext(this); } finally { configLock.unlock(); this.setStopped(); } + if (listeners != null) { + for (LoggerContextShutdownAware listener : listeners) { + try { + listener.contextShutdown(this); + } catch (Exception ex) { + // Ignore the exception. + } + } + } LOGGER.debug("Stopped LoggerContext[name={}, {}] with status {}", getName(), this, true); return true; } + private void unregisterJmxBeans() { + if (!isJmxDisabled()) { + try { + Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 + } catch (final LinkageError | Exception error) { + // LOG4J2-1506 Hello Android, GAE + LOGGER.error("Unable to unregister MBeans", error); + } + } + } + /** * Gets the name. * @@ -389,7 +499,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 +533,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 +547,7 @@ public void setExternalContext(final Object context) { */ @Override public Object getExternalContext() { - return this.externalContext; + return this.externalMap.get(EXTERNAL_CONTEXT_KEY); } /** @@ -419,7 +558,7 @@ public Object getExternalContext() { */ @Override public Logger getLogger(final String name) { - return getLogger(name, null); + return getLogger(name, DEFAULT_MESSAGE_FACTORY); } /** @@ -436,25 +575,32 @@ public Collection getLoggers() { } /** - * Obtains a Logger from the Context. + * Obtains a logger from the context. * - * @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. - * @return The Logger. + * @param name a logger name + * @param messageFactory a message factory to associate the logger with + * @return a logger matching the given name and message factory */ @Override - public Logger getLogger(final String name, final MessageFactory messageFactory) { - // Note: This is the only method where we add entries to the 'loggerRegistry' ivar. - Logger logger = loggerRegistry.getLogger(name, messageFactory); - if (logger != null) { - AbstractLogger.checkMessageFactory(logger, messageFactory); - return logger; - } + public Logger getLogger(final String name, @Nullable final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + return loggerRegistry.computeIfAbsent(name, effectiveMessageFactory, this::newInstance); + } - logger = newInstance(this, name, messageFactory); - loggerRegistry.putIfAbsent(name, messageFactory, logger); - return loggerRegistry.getLogger(name, messageFactory); + /** + * Gets the LoggerRegistry. + * + * @return the LoggerRegistry. + * @since 2.17.2 + * @deprecated since 2.25.0 without a replacement. + */ + @Deprecated + public org.apache.logging.log4j.spi.LoggerRegistry getLoggerRegistry() { + org.apache.logging.log4j.spi.LoggerRegistry result = + new org.apache.logging.log4j.spi.LoggerRegistry<>(); + loggerRegistry.getLoggers().forEach(l -> result.putIfAbsent(l.getName(), l.getMessageFactory(), l)); + return result; } /** @@ -465,7 +611,7 @@ public Logger getLogger(final String name, final MessageFactory messageFactory) */ @Override public boolean hasLogger(final String name) { - return loggerRegistry.hasLogger(name); + return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY); } /** @@ -475,8 +621,10 @@ public boolean hasLogger(final String name) { * @return True if the Logger exists, false otherwise. */ @Override - public boolean hasLogger(final String name, final MessageFactory messageFactory) { - return loggerRegistry.hasLogger(name, messageFactory); + public boolean hasLogger(final String name, @Nullable final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + return loggerRegistry.hasLogger(name, effectiveMessageFactory); } /** @@ -493,7 +641,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"); @@ -554,12 +704,8 @@ private Configuration setConfiguration(final Configuration config) { firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config)); - try { - Server.reregisterMBeansAfterReconfigure(); - } catch (final LinkageError | Exception e) { - // LOG4J2-716: Android has no java.lang.management - LOGGER.error("Could not reconfigure JMX", e); - } + registerJmxBeans(); + // AsyncLoggers update their nanoClock when the configuration changes Log4jLogEvent.setNanoClock(configuration.getNanoClock()); @@ -569,6 +715,17 @@ private Configuration setConfiguration(final Configuration config) { } } + private static void registerJmxBeans() { + if (!isJmxDisabled()) { + try { + Server.reregisterMBeansAfterReconfigure(); + } catch (final LinkageError | Exception error) { + // LOG4J2-716: Android has no java.lang.management + LOGGER.error("Could not reconfigure JMX", error); + } + } + } + private void firePropertyChangeEvent(final PropertyChangeEvent event) { for (final PropertyChangeListener listener : propertyChangeListeners) { listener.propertyChange(event); @@ -609,21 +766,36 @@ 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 context[name={}] at URI {} ({}) with optional ClassLoader: {}", + contextName, + configURI, + this, + cl); + final Configuration instance = + ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); if (instance == null) { - LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl); + LOGGER.error( + "Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", + contextName, + configURI, + cl); } else { setConfiguration(instance); /* * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { * 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); + 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); } } @@ -636,6 +808,17 @@ public void reconfigure() { reconfigure(configLocation); } + public void reconfigure(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. */ @@ -650,9 +833,7 @@ public void updateLoggers() { */ public void updateLoggers(final Configuration config) { final Configuration old = this.configuration; - for (final Logger logger : loggerRegistry.getLoggers()) { - logger.updateConfiguration(config); - } + loggerRegistry.getLoggers().forEach(logger -> logger.updateConfiguration(config)); firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, old, config)); } @@ -663,24 +844,44 @@ 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(). + private void initApiModule() { + ThreadContextMapFactory + .init(); // Or make public and call ThreadContext.init() which calls ThreadContextMapFactory.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); + private Logger newInstance(final String name, final MessageFactory messageFactory) { + return newInstance(this, name, messageFactory); } + /** + * Callback to create a new logger. + * + * @param context The {@link LoggerContext} this logger is associated with, never {@code null}. + * @param messageFactory The message factory to be used, never {@code null}. + * @param name The logger name, never {@code null}. + * @return A new logger instance. + */ + protected Logger newInstance(LoggerContext context, String name, MessageFactory messageFactory) { + return new Logger(context, name, messageFactory); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContextAccessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContextAccessor.java index c46df7c1a2f..68192e9f8e6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContextAccessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContextAccessor.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core; @@ -22,5 +22,4 @@ public interface LoggerContextAccessor { LoggerContext getLoggerContext(); - } 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..3befc23a053 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 @@ -1,33 +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; - -import java.nio.charset.Charset; - -/** - * Instantiates the @{link Layout} type for String-based layouts. - */ -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(); - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.nio.charset.Charset; + +/** + * Instantiates the {@link Layout} type for String-based layouts. + */ +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..900dccbad67 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Version.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; + +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) { + 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..27530b26243 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 @@ -1,36 +1,37 @@ /* * 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.logging.log4j.core.appender; import java.io.Serializable; import java.nio.charset.Charset; import java.util.Objects; - import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.ErrorHandler; 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.Property; import org.apache.logging.log4j.core.config.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.core.filter.AbstractFilterable; +import org.apache.logging.log4j.core.impl.LocationAware; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.util.Integers; @@ -38,18 +39,18 @@ * Abstract base class for Appenders. Although Appenders do not have to extend this class, doing so will simplify their * implementation. */ -public abstract class AbstractAppender extends AbstractFilterable implements Appender { +public abstract class AbstractAppender extends AbstractFilterable implements Appender, LocationAware { /** - * 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; @@ -60,45 +61,57 @@ public abstract static class Builder> extends AbstractFilte @PluginConfiguration private Configuration configuration; + public Configuration getConfiguration() { + return configuration; + } + + public Layout getLayout() { + return layout; + } + public String getName() { return name; } - public boolean isIgnoreExceptions() { - return ignoreExceptions; + public Layout getOrCreateLayout() { + if (layout == null) { + return PatternLayout.createDefaultLayout(configuration); + } + return layout; } - public Layout getLayout() { + public Layout getOrCreateLayout(final Charset charset) { + if (layout == null) { + return PatternLayout.newBuilder() + .setCharset(charset) + .setConfiguration(configuration) + .build(); + } return layout; } - public B withName(final String name) { - this.name = name; + public boolean isIgnoreExceptions() { + return ignoreExceptions; + } + + public B setConfiguration(final Configuration configuration) { + this.configuration = configuration; 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(); } - public Layout getOrCreateLayout() { - if (layout == null) { - return PatternLayout.createDefaultLayout(); - } - return layout; - } - - public Layout getOrCreateLayout(final Charset charset) { - if (layout == null) { - return PatternLayout.newBuilder().withCharset(charset).build(); - } - return layout; + public B setName(final String name) { + this.name = name; + return asBuilder(); } /** @@ -110,62 +123,119 @@ public B withConfiguration(final Configuration configuration) { return asBuilder(); } - public B setConfiguration(final Configuration configuration) { - this.configuration = configuration; - return asBuilder(); + /** + * @deprecated use {@link #setIgnoreExceptions(boolean)}. + */ + @Deprecated + public B withIgnoreExceptions(final boolean ignoreExceptions) { + return setIgnoreExceptions(ignoreExceptions); } - public Configuration getConfiguration() { - return configuration; + /** + * @deprecated use {@link #setLayout(Layout)}. + */ + @Deprecated + public B withLayout(final Layout layout) { + return setLayout(layout); + } + + /** + * @deprecated use {@link #setName(String)}. + */ + @Deprecated + public B withName(final String name) { + return setName(name); + } + + public String getErrorPrefix() { + final Class appenderClass = getClass().getEnclosingClass(); + final String name = getName(); + final StringBuilder sb = + new StringBuilder(appenderClass != null ? appenderClass.getSimpleName() : "Appender"); + if (name != null) { + sb.append(" '").append(name).append("'"); + } + return sb.toString(); + } + } + + public static int parseInt(final String s, final int defaultValue) { + try { + return Integers.parseInt(s, defaultValue); + } catch (final NumberFormatException e) { + LOGGER.error("Could not parse \"{}\" as an integer, using default value {}: {}", s, defaultValue, e); + return defaultValue; } - } - + private final String name; private final boolean ignoreExceptions; private final Layout layout; - private ErrorHandler handler = new DefaultErrorHandler(this); + + private volatile ErrorHandler handler = new DefaultErrorHandler(this); + + @Override + public boolean requiresLocation() { + return layout instanceof LocationAware && ((LocationAware) layout).requiresLocation(); + } /** * 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[])} */ - protected AbstractAppender(final String name, final Filter filter, final Layout layout, + @Deprecated + protected AbstractAppender( + final String name, + final Filter filter, + final Layout layout, final boolean ignoreExceptions) { - super(filter); + this(name, filter, layout, ignoreExceptions, 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. + * @since 2.11.2 + */ + 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; } - public static int parseInt(final String s, final int defaultValue) { - try { - return Integers.parseInt(s, defaultValue); - } catch (final NumberFormatException e) { - LOGGER.error("Could not parse \"{}\" as an integer, using default value {}: {}", s, defaultValue, e); - return defaultValue; - } - } - /** * 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 +245,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 +256,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 +266,7 @@ public void error(final String msg, final Throwable t) { /** * Returns the ErrorHandler, if any. - * + * * @return The ErrorHandler. */ @Override @@ -206,7 +276,7 @@ public ErrorHandler getHandler() { /** * Returns the Layout for the appender. - * + * * @return The Layout used to format the event. */ @Override @@ -216,7 +286,7 @@ public Layout getLayout() { /** * Returns the name of the Appender. - * + * * @return The name of the Appender. */ @Override @@ -237,16 +307,13 @@ 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"); - } - if (isStarted()) { - LOGGER.error("The handler cannot be changed once the appender is started"); return; } this.handler = handler; @@ -254,7 +321,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. @@ -267,5 +334,4 @@ protected Serializable toSerializable(final LogEvent event) { public String toString() { return name; } - } 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..a059f6bf386 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,9 +20,9 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; - 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.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.net.Advertiser; @@ -34,7 +34,7 @@ public abstract class AbstractFileAppender extend /** * Builds FileAppender instances. - * + * * @param * The type to build */ @@ -104,64 +104,178 @@ public String getFileGroup() { return fileGroup; } + /** + * @since 2.26.0 + */ + public B setAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAdvertiseUri(final String advertiseUri) { + this.advertiseUri = advertiseUri; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAppend(final boolean append) { + this.append = append; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileName(final String fileName) { + this.fileName = fileName; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setCreateOnDemand(final boolean createOnDemand) { + this.createOnDemand = createOnDemand; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setLocking(final boolean locking) { + this.locking = locking; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFilePermissions(final String filePermissions) { + this.filePermissions = filePermissions; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileOwner(final String fileOwner) { + this.fileOwner = fileOwner; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileGroup(final String fileGroup) { + this.fileGroup = fileGroup; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setAdvertise(boolean)}. + */ + @Deprecated public B withAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAdvertiseUri(String)}. + */ + @Deprecated public B withAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAppend(boolean)}. + */ + @Deprecated public B withAppend(final boolean append) { this.append = append; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileName(String)}. + */ + @Deprecated public B withFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setCreateOnDemand(boolean)}. + */ + @Deprecated public B withCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setLocking(boolean)}. + */ + @Deprecated public B withLocking(final boolean locking) { this.locking = locking; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFilePermissions(String)}. + */ + @Deprecated public B withFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileOwner(String)}. + */ + @Deprecated public B withFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileGroup(String)}. + */ + @Deprecated public B withFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } - } - + private final String fileName; private final Advertiser advertiser; private final Object advertisement; - 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) { - - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + 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 Property[] properties) { + + 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..40494195428 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 @@ -1,32 +1,34 @@ /* * 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.logging.log4j.core.appender; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; 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.AbstractLifeCycle; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationException; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.status.StatusLogger; @@ -36,11 +38,38 @@ * 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 { + /** + * Implementations should extend this class for passing data between the getManager method and the manager factory + * class. + */ + protected abstract static class AbstractFactoryData { + + private final Configuration configuration; + + /** + * Constructs the base factory data. + * + * @param configuration Configuration creating this instance. + */ + protected AbstractFactoryData(final Configuration configuration) { + this.configuration = configuration; + } + + /** + * Gets my configuration. + * + * @return my configuration. + */ + public Configuration getConfiguration() { + return configuration; + } + } + /** * Allow subclasses access to the status logger without creating another instance. */ @@ -84,7 +113,11 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { MAP.remove(name); LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName()); stopped = releaseSub(timeout, timeUnit); - LOGGER.debug("Shut down {} {}, all resources released: {}", this.getClass().getSimpleName(), getName(), stopped); + LOGGER.debug( + "Shut down {} {}, all resources released: {}", + this.getClass().getSimpleName(), + getName(), + stopped); } } finally { LOCK.unlock(); @@ -103,14 +136,14 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { */ // @SuppressWarnings("resource"): this is a factory method, the resource is allocated and released elsewhere. @SuppressWarnings("resource") - public static M getManager(final String name, final ManagerFactory factory, - final T data) { + public static M getManager( + final String name, final ManagerFactory factory, final T data) { LOCK.lock(); try { @SuppressWarnings("unchecked") M manager = (M) MAP.get(name); if (manager == null) { - manager = factory.createManager(name, data); + manager = Objects.requireNonNull(factory, "factory").createManager(name, data); if (manager == null) { throw new IllegalStateException("ManagerFactory [" + factory + "] unable to create manager for [" + name + "] with data [" + data + "]"); @@ -126,6 +159,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. } @@ -160,8 +198,19 @@ protected static M narrow(final Class narrowClass return (M) manager; } throw new ConfigurationException( - "Configuration has multiple incompatible Appenders pointing to the same resource '" + - manager.getName() + "'"); + "Configuration has multiple incompatible Appenders pointing to the same resource '" + manager.getName() + + "'"); + } + + protected static StatusLogger logger() { + return StatusLogger.getLogger(); + } + + /** + * For testing purposes. + */ + static int getManagerCount() { + return MAP.size(); } /** @@ -219,9 +268,25 @@ public Map getContentFormat() { return new HashMap<>(); } + /** + * Gets my configuration's StrSubstitutor or null. + * + * @return my configuration's StrSubstitutor or null. + */ + protected StrSubstitutor getStrSubstitutor() { + if (loggerContext == null) { + return null; + } + final Configuration configuration = loggerContext.getConfiguration(); + if (configuration == null) { + return null; + } + return configuration.getStrSubstitutor(); + } + protected void log(final Level level, final String message, final Throwable throwable) { - final Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}", - getClass().getSimpleName(), getName(), message, throwable); + final Message m = LOGGER.getMessageFactory() + .newMessage("{} {} {}: {}", getClass().getSimpleName(), getName(), message, throwable); LOGGER.log(level, m, throwable); } @@ -236,5 +301,4 @@ protected void logError(final String message, final Throwable throwable) { protected void logWarn(final String message, final Throwable throwable) { log(Level.WARN, message, throwable); } - } 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..e54d0bd5c91 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 @@ -1,27 +1,27 @@ /* * 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.logging.log4j.core.appender; import java.io.Serializable; import java.util.concurrent.TimeUnit; - 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.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.util.Constants; @@ -33,12 +33,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 +59,41 @@ public boolean isBufferedIo() { public boolean isImmediateFlush() { return immediateFlush; } - + + public B setImmediateFlush(final boolean immediateFlush) { + this.immediateFlush = immediateFlush; + return asBuilder(); + } + + public B setBufferedIo(final boolean bufferedIo) { + this.bufferedIo = bufferedIo; + return asBuilder(); + } + + public B setBufferSize(final int bufferSize) { + this.bufferSize = bufferSize; + return asBuilder(); + } + + @Deprecated public B withImmediateFlush(final boolean immediateFlush) { this.immediateFlush = immediateFlush; return asBuilder(); } - + + @Deprecated public B withBufferedIo(final boolean bufferedIo) { this.bufferedIo = bufferedIo; return asBuilder(); } + @Deprecated public B withBufferSize(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 @@ -94,10 +111,43 @@ public B withBufferSize(final int bufferSize) { * @param name The name of the Appender. * @param layout The layout to format the message. * @param manager The OutputStreamManager. + * @deprecated Use {@link #AbstractOutputStreamAppender(String, Layout, Filter, boolean, boolean, Property[], OutputStreamManager)} + */ + @Deprecated + 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, Property.EMPTY_ARRAY); + this.manager = manager; + this.immediateFlush = immediateFlush; + } + + /** + * Instantiates a WriterAppender and set the output destination to a new {@link java.io.OutputStreamWriter} + * initialized with os as its {@link java.io.OutputStream}. + * + * @param name The name of the Appender. + * @param layout The layout to format the message. + * @param filter The filter to associate with the Appender. + * @param ignoreExceptions If true, exceptions will be logged and suppressed. + * If false errors will be logged and then passed to the application. + * @param immediateFlush Underlying output stream will be flushed at the end of each append operation. + * @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); + protected AbstractOutputStreamAppender( + final String name, + final Layout 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; } @@ -160,7 +210,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..1237ee173c8 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,166 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 filter + * The filter to associate with the Appender. + * @param ignoreExceptions + * If true, exceptions will be logged and suppressed. + * If false errors will be logged and then passed to the application. + * @param immediateFlush + * Underlying writer will be flushed at the end of each append operation. + * @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; + } + + /** + * Instantiates. + * + * @param name + * The name of the Appender. + * @param layout + * The layout to format the message. + * @param manager + * The OutputStreamManager. + * @deprecated Use {@link #AbstractWriterAppender(String, StringLayout, Filter, boolean, boolean, Property[], WriterManager)}. + */ + @Deprecated + 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, Property.EMPTY_ARRAY); + 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..e84e75c3c7a 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -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..9595e00b5a8 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 @@ -1,25 +1,24 @@ /* * 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.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; @@ -50,7 +49,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,8 +64,8 @@ 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 {}", - childNode, children); + LOGGER.error( + "The attribute 'name' is missing from the node {} in AppenderSet {}", childNode, children); } else { map.put(key, childNode); } @@ -82,12 +81,36 @@ public Configuration getConfiguration() { return configuration; } - public Builder withNode(@SuppressWarnings("hiding") final Node node) { + /** + * @since 2.26.0 + */ + public Builder setNode(final Node node) { this.node = node; return this; } - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + /** + * @since 2.26.0 + */ + public Builder setConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setNode(Node)}. + */ + @Deprecated + public Builder withNode(final Node node) { + this.node = node; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfiguration(Configuration)}. + */ + @Deprecated + public Builder withConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } @@ -96,7 +119,6 @@ public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration public String toString() { return getClass().getName() + " [node=" + node + ", configuration=" + configuration + "]"; } - } private static final StatusLogger LOGGER = StatusLogger.getLogger(); @@ -114,13 +136,13 @@ 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); + node.getAttributes().put("name", sourceAppenderName); if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) { final Node appNode = new Node(node); configuration.createConfiguration(appNode, null); @@ -132,7 +154,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..f8487071234 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -22,9 +22,6 @@ 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; @@ -41,6 +38,7 @@ 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.Property; 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; @@ -48,9 +46,9 @@ 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.LocationAware; 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.spi.AbstractLogger; /** @@ -62,10 +60,6 @@ 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 +70,23 @@ 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); + 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, + final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.queue = blockingQueueFactory.create(queueSize); this.queueSize = queueSize; this.blocking = blocking; @@ -115,14 +118,13 @@ 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,17 +133,20 @@ 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()); if (DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy) > 0) { - LOGGER.trace("AsyncAppender: {} discarded {} events.", asyncQueueFullPolicy, - DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy)); + LOGGER.trace( + "AsyncAppender: {} discarded {} events.", + asyncQueueFullPolicy, + DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy)); } setStopped(); return true; @@ -163,11 +168,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 { @@ -179,8 +184,8 @@ public void append(final LogEvent logEvent) { private boolean transfer(final LogEvent memento) { return queue instanceof TransferQueue - ? ((TransferQueue) queue).tryTransfer(memento) - : queue.offer(memento); + ? ((TransferQueue) queue).tryTransfer(memento) + : queue.offer(memento); } /** @@ -190,8 +195,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 +207,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); } @@ -223,8 +227,7 @@ public void logMessageInBackgroundThread(final LogEvent logEvent) { private boolean handleInterruptedException(final LogEvent memento) { final boolean appendSuccessful = queue.offer(memento); if (!appendSuccessful) { - LOGGER.warn("Interrupted while waiting for a free slot in the AsyncAppender LogEvent-queue {}", - getName()); + LOGGER.warn("Interrupted while waiting for a free slot in the AsyncAppender LogEvent-queue {}", getName()); } // set the interrupted flag again. Thread.currentThread().interrupt(); @@ -258,10 +261,17 @@ private void logToErrorAppenderIfNecessary(final boolean appendSuccessful, final * @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) { + 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; @@ -270,8 +280,19 @@ public static AsyncAppender createAppender(final AppenderRef[] appenderRefs, fin LOGGER.error("No appender references provided to AsyncAppender {}", name); } - return new AsyncAppender(name, filter, appenderRefs, errorRef, size, blocking, ignoreExceptions, - shutdownTimeout, config, includeLocation, new ArrayBlockingQueueFactory()); + return new AsyncAppender( + name, + filter, + appenderRefs, + errorRef, + size, + blocking, + ignoreExceptions, + shutdownTimeout, + config, + includeLocation, + new ArrayBlockingQueueFactory(), + null); } @PluginBuilderFactory @@ -279,7 +300,8 @@ 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 +327,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 +371,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 +388,19 @@ 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()); } } @@ -509,6 +437,15 @@ public boolean isBlocking() { return blocking; } + /** + * Gets all Appenders. + * + * @return a list of Appenders. + */ + public List getAppenders() { + return dispatcher.getAppenders(); + } + /** * Returns the name of the appender that any errors are logged to or {@code null}. * @@ -525,4 +462,32 @@ 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(); + } + + @Override + public boolean requiresLocation() { + if (!includeLocation) { + return false; + } + for (final Appender appender : this.getAppenders()) { + if (appender instanceof LocationAware && ((LocationAware) appender).requiresLocation()) { + return true; + } + } + if (errorAppender != null + && errorAppender.getAppender() instanceof LocationAware + && ((LocationAware) errorAppender.getAppender()).requiresLocation()) { + return true; + } + return false; + } } 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..ed962f5952c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppenderEventDispatcher.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.appender; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +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; + +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(); + } + + /** + * Gets all Appenders. + * + * @return a list of Appenders. + */ + List getAppenders() { + return appenders.stream().map(AppenderControl::getAppender).collect(Collectors.toList()); + } + + @Override + public void run() { + LOGGER.trace("{} has started.", getName()); + dispatchAll(); + dispatchRemaining(); + } + + private void dispatchAll() { + while (!stoppedRef.get()) { + 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..993b0a23e8d 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -30,7 +30,6 @@ public class ConfigurationFactoryData { public final Configuration configuration; public ConfigurationFactoryData(final Configuration configuration) { - super(); this.configuration = configuration; } @@ -40,11 +39,10 @@ public Configuration getConfiguration() { /** * Gets the LoggerContext from the Configuration or null. - * + * * @return the LoggerContext from the Configuration or null. */ public LoggerContext getLoggerContext() { return configuration != null ? configuration.getLoggerContext() : null; } - } 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..d5445456d84 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,26 +20,20 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintStream; import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; import java.nio.charset.Charset; 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.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.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.util.CloseShieldOutputStream; -import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -52,12 +46,15 @@ * print byte streams. *

*/ -@Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin( + name = ConsoleAppender.PLUGIN_NAME, + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) 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,15 +85,21 @@ public Charset getDefaultCharset() { public abstract Charset getDefaultCharset(); - protected Charset getCharset(final String property, Charset defaultCharset) { - return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, 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); + private ConsoleAppender( + final String name, + final Layout layout, + final Filter filter, + final OutputStreamManager manager, + final boolean ignoreExceptions, + final Target target, + final Property[] properties) { + super(name, layout, filter, ignoreExceptions, true, properties, manager); this.target = target; } @@ -105,32 +108,30 @@ private ConsoleAppender(final String name, final Layout * * @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 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 ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they + * @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(Layout layout, + public static ConsoleAppender createAppender( + Layout layout, final Filter filter, - final String targetStr, + final String target, 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); + final String ignoreExceptions) { + return newBuilder() + .setLayout(layout) + .setFilter(filter) + .setTarget(target == null ? DEFAULT_TARGET : Target.valueOf(target)) + .setName(name) + .setFollow(Boolean.parseBoolean(follow)) + .setIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true)) + .build(); } /** @@ -158,26 +159,27 @@ public static ConsoleAppender createAppender( 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); + return newBuilder() + .setLayout(layout) + .setFilter(filter) + .setTarget(target == null ? DEFAULT_TARGET : target) + .setName(name) + .setFollow(follow) + .setDirect(direct) + .setIgnoreExceptions(ignoreExceptions) + .build(); } 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); + return new ConsoleAppender( + "DefaultConsole-" + COUNT.incrementAndGet(), + layout, + null, + getDefaultManager(layout), + true, + DEFAULT_TARGET, + null); } @PluginBuilderFactory @@ -219,71 +221,52 @@ public B setDirect(final boolean shouldDirect) { @Override public ConsoleAppender build() { - if (follow && direct) { - throw new IllegalArgumentException("Cannot use both follow and direct on ConsoleAppender '" + getName() + "'"); + if (!isValid()) { + return null; + } + if (direct && follow) { + LOGGER.error("Cannot use both `direct` and `follow` on ConsoleAppender."); + return null; } final Layout layout = getOrCreateLayout(target.getDefaultCharset()); - return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout), - isIgnoreExceptions(), target); + + OutputStream stream = direct + ? getDirectOutputStream(target) + : follow ? getFollowOutputStream(target) : getDefaultOutputStream(target); + + final String managerName = target.name() + '.' + follow + '.' + direct; + final OutputStreamManager manager = + OutputStreamManager.getManager(managerName, new FactoryData(stream, managerName, layout), factory); + return new ConsoleAppender( + getName(), layout, getFilter(), manager, isIgnoreExceptions(), target, getPropertyArray()); } } - private static OutputStreamManager getDefaultManager(final Target target, final boolean follow, final boolean direct, - final Layout layout) { - final OutputStream os = getOutputStream(follow, direct, target); - + private static OutputStreamManager getDefaultManager(final Layout layout) { + final OutputStream os = getDefaultOutputStream(ConsoleAppender.DEFAULT_TARGET); // LOG4J2-1176 DefaultConfiguration should not share OutputStreamManager instances to avoid memory leaks. - final String managerName = target.name() + '.' + follow + '.' + direct + "-" + COUNT.get(); + final String managerName = ConsoleAppender.DEFAULT_TARGET.name() + ".false.false-" + COUNT.get(); return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); } - private static OutputStreamManager getManager(final Target target, final boolean follow, final boolean direct, - final Layout layout) { - final OutputStream os = getOutputStream(follow, direct, target); - final String managerName = target.name() + '.' + follow + '.' + direct; - return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); + private static OutputStream getDefaultOutputStream(Target target) { + return new CloseShieldOutputStream(target == Target.SYSTEM_OUT ? System.out : System.err); } - private static OutputStream getOutputStream(final boolean follow, final boolean direct, final Target target) { - final String enc = Charset.defaultCharset().name(); - OutputStream outputStream; - try { - // @formatter:off - outputStream = target == Target.SYSTEM_OUT ? - direct ? new FileOutputStream(FileDescriptor.out) : - (follow ? new PrintStream(new SystemOutStream(), true, enc) : System.out) : - direct ? new FileOutputStream(FileDescriptor.err) : - (follow ? new PrintStream(new SystemErrStream(), true, enc) : System.err); - // @formatter:on - outputStream = new CloseShieldOutputStream(outputStream); - } 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) { - 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 Constructor constructor = clazz.getConstructor(OutputStream.class); - return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream)); - } catch (final ClassNotFoundException cnfe) { - LOGGER.debug("Jansi is not installed, cannot find {}", JANSI_CLASS); - } 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()); - } - return outputStream; + private static OutputStream getDirectOutputStream(Target target) { + return new CloseShieldOutputStream( + new FileOutputStream(target == Target.SYSTEM_OUT ? FileDescriptor.out : FileDescriptor.err)); + } + + private static OutputStream getFollowOutputStream(Target target) { + return target == Target.SYSTEM_OUT ? new SystemOutStream() : new SystemErrStream(); } /** * An implementation of OutputStream that redirects to the current System.err. */ private static class SystemErrStream extends OutputStream { - public SystemErrStream() { - } + public SystemErrStream() {} @Override public void close() { @@ -315,8 +298,7 @@ public void write(final int b) { * An implementation of OutputStream that redirects to the current System.out. */ private static class SystemOutStream extends OutputStream { - public SystemOutStream() { - } + public SystemOutStream() {} @Override public void close() { @@ -387,5 +369,4 @@ public OutputStreamManager createManager(final String name, final FactoryData da public Target getTarget() { return target; } - } 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..09cb3522893 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 @@ -1,28 +1,28 @@ /* * 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.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.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.PluginFactory; @@ -31,12 +31,16 @@ * No-Operation Appender that counts events. */ @Plugin(name = "CountingNoOp", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) -public class CountingNoOpAppender extends AbstractAppender { +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); + } + + private CountingNoOpAppender(final String name, final Layout layout, final Property[] properties) { + super(name, null, layout, true, properties); } public long getCount() { @@ -53,6 +57,6 @@ public void append(final LogEvent event) { */ @PluginFactory public static CountingNoOpAppender createAppender(@PluginAttribute("name") final String name) { - return new CountingNoOpAppender(Objects.requireNonNull(name), null); + return new CountingNoOpAppender(Objects.requireNonNull(name), null, Property.EMPTY_ARRAY); } } 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..d41d41b961f 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 @@ -1,23 +1,24 @@ /* * 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.logging.log4j.core.appender; -import java.util.concurrent.TimeUnit; +import static java.util.Objects.requireNonNull; +import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.ErrorHandler; @@ -25,72 +26,89 @@ import org.apache.logging.log4j.status.StatusLogger; /** - * + * 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; + } } } 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..27b035fad56 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,7 +20,6 @@ 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; @@ -28,12 +27,14 @@ 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.Property; 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.impl.LocationAware; import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.core.util.Constants; @@ -59,18 +60,24 @@ public final class FailoverAppender extends AbstractAppender { private final long intervalNanos; - private volatile long nextCheckNanos = 0; - - 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); + 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, + 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(); @@ -128,8 +135,8 @@ private void callAppender(final LogEvent event) { } private void failover(final LogEvent event, final Exception ex) { - final RuntimeException re = ex != null ? - (ex instanceof LoggingException ? (LoggingException) ex : new LoggingException(ex)) : null; + final RuntimeException re = + ex != null ? (ex instanceof LoggingException ? (LoggingException) ex : new LoggingException(ex)) : null; boolean written = false; Exception failoverException = null; for (final AppenderControl control : failoverAppenders) { @@ -185,7 +192,8 @@ public static FailoverAppender createAppender( @PluginAttribute("primary") final String primary, @PluginElement("Failovers") final String[] failovers, @PluginAliases("retryInterval") // deprecated - @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds, + @PluginAttribute("retryIntervalSeconds") + final String retryIntervalSeconds, @PluginConfiguration final Configuration config, @PluginElement("Filter") final Filter filter, @PluginAttribute("ignoreExceptions") final String ignore) { @@ -213,6 +221,23 @@ public static FailoverAppender createAppender( 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, null); + } + + @Override + public boolean requiresLocation() { + if (primary != null + && primary.getAppender() instanceof LocationAware + && ((LocationAware) primary.getAppender()).requiresLocation()) { + return true; + } + for (final AppenderControl control : failoverAppenders) { + final Appender appender = control.getAppender(); + if (appender instanceof LocationAware && ((LocationAware) appender).requiresLocation()) { + return true; + } + } + return false; } } 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..e4092f08410 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -27,7 +27,7 @@ /** * The array of failover Appenders. */ -@Plugin(name = "failovers", category = Core.CATEGORY_NAME) +@Plugin(name = "Failovers", category = Core.CATEGORY_NAME) public final class FailoversPlugin { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -35,8 +35,7 @@ public final class FailoversPlugin { /** * Prevent instantiation. */ - private FailoversPlugin() { - } + private FailoversPlugin() {} /** * Returns the appender references. 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..a9ab3508760 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,12 +20,12 @@ 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.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; @@ -37,14 +37,18 @@ /** * File Appender. */ -@Plugin(name = FileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin( + name = FileAppender.PLUGIN_NAME, + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) public final class FileAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "File"; /** * Builds FileAppender instances. - * + * * @param * The type to build */ @@ -81,6 +85,9 @@ public static class Builder> extends AbstractOutputStreamAp @Override public FileAppender build() { + if (!isValid()) { + return null; + } boolean bufferedIo = isBufferedIo(); final int bufferSize = getBufferSize(); if (locking && bufferedIo) { @@ -92,14 +99,33 @@ public FileAppender build() { } final Layout layout = getOrCreateLayout(); - final FileManager manager = FileManager.getFileManager(fileName, append, locking, bufferedIo, createOnDemand, - advertiseUri, layout, bufferSize, filePermissions, fileOwner, fileGroup, getConfiguration()); + final FileManager manager = FileManager.getFileManager( + fileName, + append, + locking, + bufferedIo, + createOnDemand, + advertiseUri, + layout, + bufferSize, + filePermissions, + fileOwner, + fileGroup, + getConfiguration()); if (manager == null) { return null; } - return new FileAppender(getName(), layout, getFilter(), manager, fileName, isIgnoreExceptions(), - !bufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null); + return new FileAppender( + getName(), + layout, + getFilter(), + manager, + fileName, + isIgnoreExceptions(), + !bufferedIo || isImmediateFlush(), + advertise ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); } public String getAdvertiseUri() { @@ -138,55 +164,162 @@ public String getFileGroup() { return fileGroup; } + /** + * @since 2.26.0 + */ + public B setAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAdvertiseUri(final String advertiseUri) { + this.advertiseUri = advertiseUri; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAppend(final boolean append) { + this.append = append; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileName(final String fileName) { + this.fileName = fileName; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setCreateOnDemand(final boolean createOnDemand) { + this.createOnDemand = createOnDemand; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setLocking(final boolean locking) { + this.locking = locking; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFilePermissions(final String filePermissions) { + this.filePermissions = filePermissions; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileOwner(final String fileOwner) { + this.fileOwner = fileOwner; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileGroup(final String fileGroup) { + this.fileGroup = fileGroup; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setAdvertise(boolean)}. + */ + @Deprecated public B withAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAdvertiseUri(String)}. + */ + @Deprecated public B withAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAppend(boolean)}. + */ + @Deprecated public B withAppend(final boolean append) { this.append = append; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileName(String)}. + */ + @Deprecated public B withFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setCreateOnDemand(boolean)}. + */ + @Deprecated public B withCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setLocking(boolean)}. + */ + @Deprecated public B withLocking(final boolean locking) { this.locking = locking; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFilePermissions(String)}. + */ + @Deprecated public B withFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileOwner(String)}. + */ + @Deprecated public B withFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileGroup(String)}. + */ + @Deprecated public B withFileGroup(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. @@ -226,39 +359,46 @@ public static > FileAppender createAppender( 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(); + .setAdvertise(Boolean.parseBoolean(advertise)) + .setAdvertiseUri(advertiseUri) + .setAppend(Booleans.parseBoolean(append, true)) + .setBufferedIo(Booleans.parseBoolean(bufferedIo, true)) + .setBufferSize(Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE)) + .setConfiguration(config) + .setFileName(fileName) + .setFilter(filter) + .setIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true)) + .setImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) + .setLayout(layout) + .setLocking(Boolean.parseBoolean(locking)) + .setName(name) + .build(); // @formatter:on } - + @PluginBuilderFactory public static > B newBuilder() { return new Builder().asBuilder(); } - + private final String fileName; private final Advertiser advertiser; private final Object advertisement; - 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) { - - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + 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 Property[] properties) { + + 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..46123ce074a 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -25,24 +26,25 @@ 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; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.FileUtils; - /** * Manages actual File I/O for File Appenders. */ @@ -64,8 +66,14 @@ public class FileManager extends OutputStreamManager { * @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, + 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])); } @@ -75,8 +83,14 @@ protected FileManager(final String fileName, final OutputStream os, final boolea * @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, + 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; @@ -95,9 +109,17 @@ protected FileManager(final String fileName, final OutputStream os, final boolea * @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) { + 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; @@ -113,9 +135,22 @@ protected FileManager(final LoggerContext loggerContext, final String fileName, /** * @since 2.9 */ - 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 String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader, + @SuppressFBWarnings( + value = "OVERLY_PERMISSIVE_FILE_PERMISSION", + justification = "File permissions are specified in the configuration file.") + 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 String filePermissions, + final String fileOwner, + final String fileGroup, + final boolean writeHeader, final ByteBuffer buffer) { super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); this.isAppend = append; @@ -164,36 +199,75 @@ 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. */ - public static FileManager getFileManager(final String fileName, final boolean append, boolean locking, - final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri, + public static FileManager getFileManager( + final String fileName, + final boolean append, + boolean locking, + final boolean bufferedIo, + final boolean createOnDemand, + final String advertiseUri, final Layout layout, - final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup, + final int bufferSize, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { if (locking && bufferedIo) { locking = false; } - return narrow(FileManager.class, getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize, - createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY)); + return narrow( + FileManager.class, + getManager( + fileName, + new FactoryData( + append, + locking, + bufferedIo, + bufferSize, + createOnDemand, + advertiseUri, + layout, + filePermissions, + fileOwner, + fileGroup, + configuration), + FACTORY)); } @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The destination file is specified in the configuration file.") 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 (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 { - // FileOutputStream may not create new file on all jvm + // FileOutputStream may not create new file on all JVM path.toFile().createNewFile(); FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup); @@ -204,8 +278,8 @@ protected void defineAttributeView(final Path path) { } @Override - protected synchronized void write(final byte[] bytes, final int offset, final int length, - final boolean immediateFlush) { + protected synchronized void write( + final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { if (isLocking) { try { @SuppressWarnings("resource") @@ -303,9 +377,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 +397,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 +409,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; @@ -382,9 +456,17 @@ private static class FactoryData extends ConfigurationFactoryData { * @param fileGroup File group * @param configuration the configuration */ - public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize, - final boolean createOnDemand, final String advertiseURI, final Layout layout, - final String filePermissions, final String fileOwner, final String fileGroup, + public FactoryData( + final boolean append, + final boolean locking, + final boolean bufferedIo, + final int bufferSize, + final boolean createOnDemand, + final String advertiseURI, + final Layout layout, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { super(configuration); this.append = append; @@ -412,7 +494,11 @@ private static class FileManagerFactory implements ManagerFactory The type to build */ public static class Builder> extends AbstractAppender.Builder implements org.apache.logging.log4j.core.util.Builder { @@ -72,11 +69,35 @@ public static class Builder> extends AbstractAppender.Build @Override 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); - } - + // Validate URL presence + if (url == null) { + LOGGER.error("HttpAppender requires URL to be set."); + return null; // Return null if URL is missing + } + + // Validate layout presence + if (getLayout() == null) { + LOGGER.error("HttpAppender requires a layout to be set."); + return null; // Return null if layout is missing + } + + final HttpManager httpManager = new HttpURLConnectionManager( + getConfiguration(), + getConfiguration().getLoggerContext(), + getName(), + url, + method, + connectTimeoutMillis, + readTimeoutMillis, + headers, + sslConfiguration, + verifyHostname); + + return new HttpAppender( + getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager, getPropertyArray()); + } + + // Getter and Setter methods public URL getUrl() { return url; } @@ -141,9 +162,6 @@ public B setVerifyHostname(final boolean verifyHostname) { } } - /** - * @return a builder for a HttpAppender. - */ @PluginBuilderFactory public static > B newBuilder() { return new Builder().asBuilder(); @@ -151,9 +169,14 @@ 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); + private HttpAppender( + final String name, + final Layout layout, + final Filter filter, + 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"); } @@ -184,9 +207,6 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { @Override public String toString() { - return "HttpAppender{" + - "name=" + getName() + - ", state=" + getState() + - '}'; + return "HttpAppender{" + "name=" + getName() + ", state=" + getState() + '}'; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java index dc3d2188575..08eb83054f5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpManager.java @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.appender; import java.util.Objects; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; 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..a4e98584313 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 @@ -1,22 +1,22 @@ /* * 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.logging.log4j.core.appender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -24,9 +24,7 @@ import java.net.URL; import java.nio.charset.Charset; import java.util.Objects; - import javax.net.ssl.HttpsURLConnection; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; @@ -50,12 +48,17 @@ public class HttpURLConnectionManager extends HttpManager { private final SslConfiguration sslConfiguration; private final boolean verifyHostname; - public HttpURLConnectionManager(final Configuration configuration, final LoggerContext loggerContext, final String name, - final URL url, final String method, final int connectTimeoutMillis, - final int readTimeoutMillis, - final Property[] headers, - final SslConfiguration sslConfiguration, - final boolean verifyHostname) { + public HttpURLConnectionManager( + final Configuration configuration, + final LoggerContext loggerContext, + final String name, + final URL url, + final String method, + final int connectTimeoutMillis, + final int readTimeoutMillis, + final Property[] headers, + final SslConfiguration sslConfiguration, + final boolean verifyHostname) { super(configuration, loggerContext, name); this.url = url; if (!(url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equalsIgnoreCase("https"))) { @@ -65,7 +68,7 @@ public HttpURLConnectionManager(final Configuration configuration, final LoggerC this.method = Objects.requireNonNull(method, "method"); this.connectTimeoutMillis = connectTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis; - this.headers = headers != null ? headers : new Property[0]; + this.headers = headers != null ? headers : Property.EMPTY_ARRAY; this.sslConfiguration = sslConfiguration; if (this.sslConfiguration != null && !isHttps) { throw new ConfigurationException("SSL configuration can only be specified with URL scheme https"); @@ -74,8 +77,11 @@ public HttpURLConnectionManager(final Configuration configuration, final LoggerC } @Override + @SuppressFBWarnings( + value = "URLCONNECTION_SSRF_FD", + justification = "This connection URL is specified in a configuration file.") public void send(final Layout layout, final LogEvent event) throws IOException { - final HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); + final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setAllowUserInteraction(false); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); @@ -91,31 +97,31 @@ public void send(final Layout layout, final LogEvent event) throws IOExceptio } for (final Property header : headers) { urlConnection.setRequestProperty( - header.getName(), - header.isValueNeedsLookup() ? getConfiguration().getStrSubstitutor().replace(event, header.getValue()) : header.getValue()); + header.getName(), header.evaluate(getConfiguration().getStrSubstitutor())); } if (sslConfiguration != null) { - ((HttpsURLConnection)urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory()); + ((HttpsURLConnection) urlConnection) + .setSSLSocketFactory(sslConfiguration.getSslContext().getSocketFactory()); } if (isHttps && !verifyHostname) { - ((HttpsURLConnection)urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); + ((HttpsURLConnection) urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); } 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()); @@ -135,5 +141,4 @@ public void send(final Layout layout, final LogEvent event) throws IOExceptio } } } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ManagerFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ManagerFactory.java index ba20b165775..62025800731 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ManagerFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ManagerFactory.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; 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..8774263619e 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,13 +20,12 @@ 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.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; @@ -39,12 +38,16 @@ * * @since 2.1 */ -@Plugin(name = "MemoryMappedFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin( + name = "MemoryMappedFile", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender { /** * Builds RandomAccessFileAppender instances. - * + * * @param * The type to build */ @@ -81,14 +84,22 @@ public MemoryMappedFileAppender build() { return null; } final Layout layout = getOrCreateLayout(); - final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager(fileName, append, isImmediateFlush(), - actualRegionLength, advertiseURI, layout); + final MemoryMappedFileManager manager = MemoryMappedFileManager.getFileManager( + fileName, append, isImmediateFlush(), actualRegionLength, advertiseURI, layout); if (manager == null) { return null; } - return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false, - advertise ? getConfiguration().getAdvertiser() : null); + return new MemoryMappedFileAppender( + name, + layout, + getFilter(), + manager, + fileName, + isIgnoreExceptions(), + false, + advertise ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); } public B setFileName(final String fileName) { @@ -115,9 +126,8 @@ public B setAdvertiseURI(final String advertiseURI) { this.advertiseURI = advertiseURI; return asBuilder(); } - } - + 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; @@ -126,10 +136,17 @@ public B setAdvertiseURI(final String advertiseURI) { private Object advertisement; private final Advertiser advertiser; - 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); + 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, + 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 +169,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. * @@ -223,7 +222,7 @@ public static > MemoryMappedFileAppender createAppender( final String advertise, // final String advertiseURI, // final Configuration config) { - // @formatter:on + // @formatter:on final boolean isAppend = Booleans.parseBoolean(append, true); final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false); @@ -233,18 +232,18 @@ public static > MemoryMappedFileAppender createAppender( // @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(); + .setAdvertise(isAdvertise) + .setAdvertiseURI(advertiseURI) + .setAppend(isAppend) + .setConfiguration(config) + .setFileName(fileName) + .setFilter(filter) + .setIgnoreExceptions(ignoreExceptions) + .setImmediateFlush(isImmediateFlush) + .setLayout(layout) + .setName(name) + .setRegionLength(regionLength) + .build(); // @formatter:on } @@ -258,19 +257,28 @@ public static > B newBuilder() { */ private static int determineValidRegionLength(final String name, final int regionLength) { if (regionLength > MAX_REGION_LENGTH) { - LOGGER.info("MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", name, regionLength, + LOGGER.info( + "MemoryMappedAppender[{}] Reduced region length from {} to max length: {}", + name, + regionLength, MAX_REGION_LENGTH); return MAX_REGION_LENGTH; } if (regionLength < MIN_REGION_LENGTH) { - LOGGER.info("MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", name, regionLength, + LOGGER.info( + "MemoryMappedAppender[{}] Expanded region length from {} to min length: {}", + name, + regionLength, MIN_REGION_LENGTH); return MIN_REGION_LENGTH; } final int result = Integers.ceilingNextPowerOfTwo(regionLength); if (regionLength != result) { - LOGGER.info("MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", name, - regionLength, result); + LOGGER.info( + "MemoryMappedAppender[{}] Rounded up region length from {} to next power of two: {}", + name, + regionLength, + result); } return result; } 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..d5f8949adbb 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 @@ -1,66 +1,62 @@ /* * 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.logging.log4j.core.appender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.Serializable; -import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.HashMap; 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; +import org.apache.logging.log4j.core.util.internal.UnsafeUtil; +import org.apache.logging.log4j.util.Constants; -//Lines too long... -//CHECKSTYLE:OFF +// Lines too long... +// CHECKSTYLE:OFF /** * Extends OutputStreamManager but instead of using a buffered output stream, this class maps a region of a file into * memory and writes to this memory region. *

* - * @see - * http://www.codeproject.com/Tips/683614/Things-to-Know-about-Memory-Mapped-File-in-Java - * @see http://bugs.java.com/view_bug.do?bug_id=6893654 - * @see http://bugs.java.com/view_bug.do?bug_id=4724038 - * @see - * http://stackoverflow.com/questions/9261316/memory-mapped-mappedbytebuffer-or-direct-bytebuffer-for-db-implementation + * @see Things to Know about Memory Mapped File in Java + * @see JDK-6893654 + * @see JDK-4724038 + * @see Memory-Mapped MappedByteBuffer or Direct ByteBuffer for DB Implementation? * * @since 2.1 */ -//CHECKSTYLE:ON +// CHECKSTYLE:ON public class MemoryMappedFileManager extends OutputStreamManager { /** * Default length of region to map. */ static final int DEFAULT_REGION_LENGTH = 32 * 1024 * 1024; + private static final int MAX_REMAP_COUNT = 10; private static final MemoryMappedFileManagerFactory FACTORY = new MemoryMappedFileManagerFactory(); private static final double NANOS_PER_MILLISEC = 1000.0 * 1000.0; @@ -69,19 +65,25 @@ 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; - protected MemoryMappedFileManager(final RandomAccessFile file, final String fileName, final OutputStream os, - final boolean immediateFlush, final long position, final int regionLength, final String advertiseURI, - final Layout layout, final boolean writeHeader) throws IOException { - super(os, fileName, layout, writeHeader, ByteBuffer.wrap(new byte[0])); + protected MemoryMappedFileManager( + final RandomAccessFile file, + final String fileName, + final OutputStream os, + final boolean immediateFlush, + final long position, + final int regionLength, + final String advertiseURI, + final Layout layout, + final boolean writeHeader) + throws IOException { + super(os, fileName, layout, writeHeader, ByteBuffer.wrap(Constants.EMPTY_BYTE_ARRAY)); this.immediateFlush = immediateFlush; 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; @@ -98,31 +100,52 @@ protected MemoryMappedFileManager(final RandomAccessFile file, final String file * @param layout The layout. * @return A MemoryMappedFileManager for the File. */ - public static MemoryMappedFileManager getFileManager(final String fileName, final boolean append, - final boolean immediateFlush, final int regionLength, final String advertiseURI, + public static MemoryMappedFileManager getFileManager( + final String fileName, + final boolean append, + final boolean immediateFlush, + final int regionLength, + final String advertiseURI, final Layout layout) { - return narrow(MemoryMappedFileManager.class, getManager(fileName, new FactoryData(append, immediateFlush, - regionLength, advertiseURI, layout), FACTORY)); + return narrow( + MemoryMappedFileManager.class, + getManager( + fileName, + new FactoryData(append, immediateFlush, 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 - protected synchronized void write(final byte[] bytes, int offset, int length, final boolean immediateFlush) { - while (length > mappedBuffer.remaining()) { + protected synchronized void write( + final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { + int currentOffset = offset; + int currentLength = length; + while (currentLength > mappedBuffer.remaining()) { final int chunk = mappedBuffer.remaining(); - mappedBuffer.put(bytes, offset, chunk); - offset += chunk; - length -= chunk; + mappedBuffer.put(bytes, currentOffset, chunk); + currentOffset += chunk; + currentLength -= chunk; remap(); } - mappedBuffer.put(bytes, offset, length); + mappedBuffer.put(bytes, currentOffset, currentLength); // no need to call flush() if force is true, // already done in AbstractOutputStreamAppender.append @@ -134,14 +157,19 @@ private synchronized void remap() { try { unsafeUnmap(mappedBuffer); final long fileLength = randomAccessFile.length() + regionLength; - LOGGER.debug("{} {} extending {} by {} bytes to {}", getClass().getSimpleName(), getName(), getFileName(), - regionLength, fileLength); + LOGGER.debug( + "{} {} extending {} by {} bytes to {}", + getClass().getSimpleName(), + getName(), + getFileName(), + regionLength, + fileLength); final long startNanos = System.nanoTime(); randomAccessFile.setLength(fileLength); final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); - LOGGER.debug("{} {} extended {} OK in {} millis", getClass().getSimpleName(), getName(), getFileName(), - millis); + LOGGER.debug( + "{} {} extended {} OK in {} millis", getClass().getSimpleName(), getName(), getFileName(), millis); mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), offset, length); this.byteBuffer = mappedBuffer; @@ -166,8 +194,12 @@ public synchronized boolean closeOutputStream() { logError("Unable to unmap MappedBuffer", ex); } try { - LOGGER.debug("MMapAppender closing. Setting {} length to {} (offset {} + position {})", getFileName(), - length, mappingOffset, position); + LOGGER.debug( + "MMapAppender closing. Setting {} length to {} (offset {} + position {})", + getFileName(), + length, + mappingOffset, + position); randomAccessFile.setLength(length); randomAccessFile.close(); return true; @@ -177,9 +209,9 @@ public synchronized boolean closeOutputStream() { } } - public static MappedByteBuffer mmap(final FileChannel fileChannel, final String fileName, final long start, - final int size) throws IOException { - for (int i = 1;; i++) { + public static MappedByteBuffer mmap( + final FileChannel fileChannel, final String fileName, final long start, final int size) throws IOException { + for (int i = 1; ; i++) { try { LOGGER.debug("MMapAppender remapping {} start={}, size={}", fileName, start, size); @@ -210,20 +242,10 @@ public static MappedByteBuffer mmap(final FileChannel fileChannel, final String } } - private static void unsafeUnmap(final MappedByteBuffer mbb) throws PrivilegedActionException { + private static void unsafeUnmap(final MappedByteBuffer mbb) throws Exception { 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; - } - }); + UnsafeUtil.clean(mbb); final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); LOGGER.debug("MMapAppender unmapped buffer OK in {} millis", millis); } @@ -306,8 +328,12 @@ private static class FactoryData { * @param advertiseURI the URI to use when advertising the file * @param layout The layout. */ - public FactoryData(final boolean append, final boolean immediateFlush, final int regionLength, - final String advertiseURI, final Layout layout) { + public FactoryData( + final boolean append, + final boolean immediateFlush, + final int regionLength, + final String advertiseURI, + final Layout layout) { this.append = append; this.immediateFlush = immediateFlush; this.regionLength = regionLength; @@ -331,6 +357,9 @@ private static class MemoryMappedFileManagerFactory */ @SuppressWarnings("resource") @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The destination file should be specified in the configuration file.") public MemoryMappedFileManager createManager(final String name, final FactoryData data) { final File file = new File(name); if (!data.append) { @@ -345,8 +374,16 @@ public MemoryMappedFileManager createManager(final String name, final FactoryDat raf = new RandomAccessFile(name, "rw"); final long position = (data.append) ? raf.length() : 0; raf.setLength(position + data.regionLength); - return new MemoryMappedFileManager(raf, name, os, data.immediateFlush, position, data.regionLength, - data.advertiseURI, data.layout, writeHeader); + return new MemoryMappedFileManager( + raf, + name, + os, + data.immediateFlush, + position, + data.regionLength, + data.advertiseURI, + data.layout, + writeHeader); } catch (final Exception ex) { LOGGER.error("MemoryMappedFileManager (" + name + ") " + ex, ex); Closer.closeSilently(raf); 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..3a09846c81d 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 @@ -1,49 +1,54 @@ /* * 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.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.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.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) +@Plugin( + name = NullAppender.PLUGIN_NAME, + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) 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(value = "name", defaultString = "null") final String name) { return new NullAppender(name); } private NullAppender(final String name) { - super(name, null, null); - // Do nothing + super(name, null, null, true, Property.EMPTY_ARRAY); } @Override public void append(final LogEvent event) { // 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..a9f4a1e285b 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,33 +1,34 @@ /* * 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.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.Property; 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; +import org.apache.logging.log4j.core.util.NullOutputStream; /** * Appends log events to a given output stream using a layout. @@ -40,56 +41,42 @@ public final class OutputStreamAppender extends AbstractOutputStreamAppender + * The type to build. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { - - private Filter filter; + public static class Builder> extends AbstractOutputStreamAppender.Builder + implements org.apache.logging.log4j.core.util.Builder { private boolean follow = false; - private boolean ignoreExceptions = true; - - private Layout layout = PatternLayout.createDefaultLayout(); - - private String name; + private final boolean ignoreExceptions = true; private OutputStream target; @Override public OutputStreamAppender build() { - return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions); + final Layout layout = getOrCreateLayout(); + return new OutputStreamAppender( + getName(), + layout, + getFilter(), + getManager(target, follow, layout), + ignoreExceptions, + 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 Layout aLayout) { - this.layout = aLayout; - return this; + return asBuilder(); } - public Builder setName(final String aName) { - this.name = aName; - return this; - } - - public Builder setTarget(final OutputStream aTarget) { + public B setTarget(final OutputStream aTarget) { this.target = aTarget; - return this; + return asBuilder(); } } + /** * Holds data to pass to factory method. */ @@ -100,7 +87,7 @@ private static class FactoryData { /** * Builds instances. - * + * * @param os * The OutputStream. * @param type @@ -122,7 +109,7 @@ private static class OutputStreamManagerFactory implements ManagerFactory layout, final Filter filter, - final OutputStream target, final String name, final boolean follow, final boolean ignore) { + 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; @@ -167,25 +159,30 @@ public static OutputStreamAppender createAppender(Layout if (layout == null) { layout = PatternLayout.createDefaultLayout(); } - return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignore); + 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 = new CloseShieldOutputStream(target); - final String managerName = target.getClass().getName() + "@" + Integer.toHexString(target.hashCode()) + '.' - + follow; + 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); } @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); + 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) { - super(name, layout, filter, ignoreExceptions, true, manager); + 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, properties, 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..9b0a58dab52 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -23,7 +23,6 @@ import java.nio.ByteBuffer; import java.util.Objects; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.layout.ByteBufferDestination; @@ -40,15 +39,17 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe private volatile OutputStream outputStream; private boolean skipFooter; - protected OutputStreamManager(final OutputStream os, final String streamName, final Layout layout, - final boolean writeHeader) { - // Can't use new ctor because it throws an exception + protected OutputStreamManager( + final OutputStream os, final String streamName, final Layout layout, final boolean writeHeader) { 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 + protected OutputStreamManager( + final OutputStream os, + final String streamName, + final Layout layout, + final boolean writeHeader, + final int bufferSize) { this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); } @@ -57,20 +58,17 @@ protected OutputStreamManager(final OutputStream os, final String streamName, fi * @deprecated */ @Deprecated - protected OutputStreamManager(final OutputStream os, final String streamName, final Layout layout, - final boolean writeHeader, final ByteBuffer byteBuffer) { + protected OutputStreamManager( + final OutputStream os, + final String streamName, + final Layout layout, + final boolean writeHeader, + final ByteBuffer byteBuffer) { 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); - } - } + if (writeHeader) { + writeHeader(os); } this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); } @@ -78,8 +76,13 @@ protected OutputStreamManager(final OutputStream os, final String streamName, fi /** * @since 2.7 */ - protected OutputStreamManager(final LoggerContext loggerContext, final OutputStream os, final String streamName, - final boolean createOnDemand, final Layout layout, final boolean writeHeader, + protected OutputStreamManager( + final LoggerContext loggerContext, + final OutputStream os, + final String streamName, + final boolean createOnDemand, + final Layout layout, + final boolean writeHeader, final ByteBuffer byteBuffer) { super(loggerContext, streamName); if (createOnDemand && os != null) { @@ -90,15 +93,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); } } @@ -111,8 +107,8 @@ protected OutputStreamManager(final LoggerContext loggerContext, final OutputStr * @param The type of the OutputStreamManager. * @return An OutputStreamManager. */ - public static OutputStreamManager getManager(final String name, final T data, - final ManagerFactory factory) { + public static OutputStreamManager getManager( + final String name, final T data, final ManagerFactory factory) { return AbstractManager.getManager(name, factory, data); } @@ -138,6 +134,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 +180,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; } /** @@ -189,7 +188,7 @@ protected void setOutputStream(final OutputStream os) { * @param bytes The serialized Log event. * @throws AppenderLoggingException if an error occurs. */ - protected void write(final byte[] bytes) { + protected void write(final byte[] bytes) { write(bytes, 0, bytes.length, false); } @@ -199,7 +198,7 @@ protected void write(final byte[] bytes) { * @param immediateFlush If true, flushes after writing. * @throws AppenderLoggingException if an error occurs. */ - protected void write(final byte[] bytes, final boolean immediateFlush) { + protected void write(final byte[] bytes, final boolean immediateFlush) { write(bytes, 0, bytes.length, immediateFlush); } @@ -229,7 +228,8 @@ protected void write(final byte[] bytes, final int offset, final int length) { * @param immediateFlush flushes immediately after writing. * @throws AppenderLoggingException if an error occurs. */ - protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { + protected synchronized void write( + final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { if (immediateFlush && byteBuffer.position() == 0) { writeToDestination(bytes, offset, length); flushDestination(); @@ -291,10 +291,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 +316,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 +347,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 */ @@ -356,10 +360,10 @@ public ByteBuffer drain(final ByteBuffer buf) { @Override public void writeBytes(final ByteBuffer data) { if (data.remaining() == 0) { - return; + return; } synchronized (this) { - ByteBufferDestinationHelper.writeToUnsynchronized(data, this); + ByteBufferDestinationHelper.writeToUnsynchronized(data, this); } } } 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..ae7f8776f04 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,13 +20,12 @@ 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.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; @@ -37,12 +36,16 @@ /** * File Appender. */ -@Plugin(name = "RandomAccessFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin( + name = "RandomAccessFile", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) public final class RandomAccessFileAppender extends AbstractOutputStreamAppender { /** * Builds RandomAccessFileAppender instances. - * + * * @param * The type to build */ @@ -61,28 +64,40 @@ public static class Builder> extends AbstractOutputStreamAp @PluginBuilderAttribute("advertiseURI") private String advertiseURI; + public Builder() { + this.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(); final boolean immediateFlush = isImmediateFlush(); - final RandomAccessFileManager manager = RandomAccessFileManager.getFileManager(fileName, append, - immediateFlush, getBufferSize(), advertiseURI, layout, null); + final RandomAccessFileManager manager = RandomAccessFileManager.getFileManager( + fileName, append, immediateFlush, getBufferSize(), advertiseURI, layout, null); if (manager == null) { return null; } - return new RandomAccessFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), - immediateFlush, advertise ? getConfiguration().getAdvertiser() : null); + return new RandomAccessFileAppender( + name, + layout, + getFilter(), + manager, + fileName, + isIgnoreExceptions(), + immediateFlush, + advertise ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); } public B setFileName(final String fileName) { @@ -104,21 +119,26 @@ public B setAdvertiseURI(final String advertiseURI) { this.advertiseURI = advertiseURI; return asBuilder(); } - } - + private final String fileName; private Object advertisement; private final Advertiser advertiser; - private RandomAccessFileAppender(final String name, final Layout layout, - final Filter filter, final RandomAccessFileManager manager, final String filename, - final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { - - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + private RandomAccessFileAppender( + final String name, + final Layout layout, + final Filter filter, + final RandomAccessFileManager manager, + final String filename, + 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()); + final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); configuration.put("contentType", layout.getContentType()); configuration.put("name", name); @@ -139,26 +159,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. * @@ -222,20 +222,20 @@ public static > RandomAccessFileAppender createAppender( 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(); + .setAdvertise(isAdvertise) + .setAdvertiseURI(advertiseURI) + .setAppend(isAppend) + .setBufferSize(bufferSize) + .setConfiguration(configuration) + .setFileName(fileName) + .setFilter(filter) + .setIgnoreExceptions(ignoreExceptions) + .setImmediateFlush(isFlush) + .setLayout(layout) + .setName(name) + .build(); } - + /** * Creates a builder for a RandomAccessFileAppender. * @return a builder for a RandomAccessFileAppender. @@ -244,5 +244,4 @@ public static > RandomAccessFileAppender createAppender( 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..41d503d0b11 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -24,7 +25,6 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; @@ -43,15 +43,19 @@ 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, - final Layout layout, final boolean writeHeader) { + protected RandomAccessFileManager( + final LoggerContext loggerContext, + final RandomAccessFile file, + final String fileName, + final OutputStream os, + final int bufferSize, + final String advertiseURI, + final Layout layout, + final boolean writeHeader) { 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 +64,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 @@ -68,20 +72,39 @@ protected RandomAccessFileManager(final LoggerContext loggerContext, final Rando * @param configuration The configuration. * @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 Layout layout, final Configuration configuration) { - return narrow(RandomAccessFileManager.class, getManager(fileName, new FactoryData(append, - isFlush, bufferSize, advertiseURI, layout, configuration), FACTORY)); + public static RandomAccessFileManager getFileManager( + final String fileName, + final boolean append, + final boolean immediateFlush, + final int bufferSize, + final String advertiseURI, + final Layout layout, + final Configuration configuration) { + 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 protected void writeToDestination(final byte[] bytes, final int offset, final int length) { @@ -137,8 +160,7 @@ public int getBufferSize() { */ @Override public Map getContentFormat() { - final Map result = new HashMap<>( - super.getContentFormat()); + final Map result = new HashMap<>(super.getContentFormat()); result.put("fileURI", advertiseURI); return result; } @@ -160,8 +182,13 @@ private static class FactoryData extends ConfigurationFactoryData { * @param bufferSize size of the buffer * @param configuration The configuration. */ - public FactoryData(final boolean append, final boolean immediateFlush, final int bufferSize, - final String advertiseURI, final Layout layout, final Configuration configuration) { + public FactoryData( + final boolean append, + final boolean immediateFlush, + final int bufferSize, + final String advertiseURI, + final Layout layout, + final Configuration configuration) { super(configuration); this.append = append; this.immediateFlush = immediateFlush; @@ -174,8 +201,8 @@ public FactoryData(final boolean append, final boolean immediateFlush, final int /** * Factory to create a RandomAccessFileManager. */ - private static class RandomAccessFileManagerFactory implements - ManagerFactory { + private static class RandomAccessFileManagerFactory + implements ManagerFactory { /** * Create a RandomAccessFileManager. @@ -185,6 +212,9 @@ private static class RandomAccessFileManagerFactory implements * @return The RandomAccessFileManager for the File. */ @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The destination file should be specified in the configuration file.") public RandomAccessFileManager createManager(final String name, final FactoryData data) { final File file = new File(name); if (!data.append) { @@ -202,13 +232,19 @@ public RandomAccessFileManager createManager(final String name, final FactoryDat } else { raf.setLength(0); } - return new RandomAccessFileManager(data.getLoggerContext(), raf, name, - os, data.bufferSize, data.advertiseURI, data.layout, writeHeader); + return new RandomAccessFileManager( + data.getLoggerContext(), + raf, + name, + os, + data.bufferSize, + data.advertiseURI, + data.layout, + writeHeader); } catch (final Exception ex) { LOGGER.error("RandomAccessFileManager (" + name + ") " + ex, ex); } return null; } } - } 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..a78be3e4c55 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -21,7 +21,6 @@ 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; @@ -34,6 +33,7 @@ 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.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; @@ -46,14 +46,18 @@ /** * 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) +@Plugin( + name = RollingFileAppender.PLUGIN_NAME, + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) public final class RollingFileAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "RollingFile"; /** * Builds FileAppender instances. - * + * * @param * The type to build * @since 2.7 @@ -74,11 +78,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 @@ -101,58 +105,75 @@ public static class Builder> extends AbstractOutputStreamAp @Override public RollingFileAppender build() { + if (!isValid()) { + return null; + } // Even though some variables may be annotated with @Required, we must still perform validation here for // call sites that build builders programmatically. final boolean isBufferedIo = isBufferedIo(); final int bufferSize = getBufferSize(); - if (getName() == null) { - LOGGER.error("RollingFileAppender '{}': No name provided.", getName()); - return null; - } if (!isBufferedIo && bufferSize > 0) { - LOGGER.warn("RollingFileAppender '{}': The bufferSize is set to {} but bufferedIO is not true", getName(), bufferSize); - } - - if (filePattern == null) { - LOGGER.error("RollingFileAppender '{}': No file name pattern provided.", getName()); - return null; - } - - if (policy == null) { - LOGGER.error("RollingFileAppender '{}': No TriggeringPolicy provided.", getName()); - return null; + LOGGER.warn( + "RollingFileAppender '{}': The bufferSize is set to {} but bufferedIO is not true", + getName(), + bufferSize); } if (strategy == null) { if (fileName != null) { strategy = DefaultRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) - .build(); + .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) + .setConfig(getConfiguration()) + .build(); } else { strategy = DirectWriteRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) - .build(); + .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; } final Layout layout = getOrCreateLayout(); - final RollingFileManager manager = RollingFileManager.getFileManager(fileName, filePattern, append, - isBufferedIo, policy, strategy, advertiseUri, layout, bufferSize, isImmediateFlush(), - createOnDemand, filePermissions, fileOwner, fileGroup, getConfiguration()); + final RollingFileManager manager = RollingFileManager.getFileManager( + fileName, + filePattern, + append, + isBufferedIo, + policy, + strategy, + advertiseUri, + layout, + bufferSize, + isImmediateFlush(), + createOnDemand, + filePermissions, + fileOwner, + fileGroup, + getConfiguration()); if (manager == null) { return null; } manager.initialize(); - return new RollingFileAppender(getName(), layout, getFilter(), manager, fileName, filePattern, - isIgnoreExceptions(), isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null); + return new RollingFileAppender( + getName(), + layout, + getFilter(), + manager, + fileName, + filePattern, + isIgnoreExceptions(), + !isBufferedIo || isImmediateFlush(), + advertise ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); } public String getAdvertiseUri() { @@ -191,31 +212,103 @@ public String getFileGroup() { return fileGroup; } + /** + * @since 2.26.0 + */ + public B setAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAdvertiseUri(final String advertiseUri) { + this.advertiseUri = advertiseUri; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAppend(final boolean append) { + this.append = append; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileName(final String fileName) { + this.fileName = fileName; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setCreateOnDemand(final boolean createOnDemand) { + this.createOnDemand = createOnDemand; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setLocking(final boolean locking) { + this.locking = locking; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setAdvertise(boolean)}. + */ + @Deprecated public B withAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAdvertiseUri(String)}. + */ + @Deprecated public B withAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAppend(boolean)}. + */ + @Deprecated public B withAppend(final boolean append) { this.append = append; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileName(String)}. + */ + @Deprecated public B withFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setCreateOnDemand(boolean)}. + */ + @Deprecated public B withCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setLocking(boolean)}. + */ + @Deprecated public B withLocking(final boolean locking) { this.locking = locking; return asBuilder(); @@ -233,38 +326,109 @@ public RolloverStrategy getStrategy() { return strategy; } + /** + * @since 2.26.0 + */ + public B setFilePattern(final String filePattern) { + this.filePattern = filePattern; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setPolicy(final TriggeringPolicy policy) { + this.policy = policy; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setStrategy(final RolloverStrategy strategy) { + this.strategy = strategy; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFilePermissions(final String filePermissions) { + this.filePermissions = filePermissions; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileOwner(final String fileOwner) { + this.fileOwner = fileOwner; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileGroup(final String fileGroup) { + this.fileGroup = fileGroup; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setFilePattern(String)}. + */ + @Deprecated public B withFilePattern(final String filePattern) { this.filePattern = filePattern; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setPolicy(TriggeringPolicy)}. + */ + @Deprecated public B withPolicy(final TriggeringPolicy policy) { this.policy = policy; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setStrategy(RolloverStrategy)}. + */ + @Deprecated public B withStrategy(final RolloverStrategy strategy) { this.strategy = strategy; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFilePermissions(String)}. + */ + @Deprecated public B withFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileOwner(String)}. + */ + @Deprecated public B withFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileGroup(String)}. + */ + @Deprecated public B withFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } - } - + private static final int DEFAULT_BUFFER_SIZE = 8192; private final String fileName; @@ -272,10 +436,18 @@ public B withFileGroup(final String fileGroup) { private Object advertisement; private final Advertiser advertiser; - 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); + 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, + final Property[] properties) { + super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.put("contentType", layout.getContentType()); @@ -300,7 +472,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { /** * Writes the log entry rolling over the file when required. - + * * @param event The LogEvent. */ @Override @@ -374,34 +546,34 @@ public static > RollingFileAppender createAppender( final String advertise, final String advertiseUri, final Configuration config) { - // @formatter:on + // @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) + .setAdvertise(Boolean.parseBoolean(advertise)) + .setAdvertiseUri(advertiseUri) + .setAppend(Booleans.parseBoolean(append, true)) + .setBufferedIo(Booleans.parseBoolean(bufferedIO, true)) + .setBufferSize(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) + .setFileName(fileName) + .setFilePattern(filePattern) + .setFilter(filter) + .setIgnoreExceptions(Booleans.parseBoolean(ignore, true)) + .setImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) + .setLayout(layout) + .setCreateOnDemand(false) + .setLocking(false) + .setName(name) + .setPolicy(policy) + .setStrategy(strategy) .build(); // @formatter:on } /** * Creates a new Builder. - * + * * @return a new Builder. * @since 2.7 */ 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..5e7361f68ff 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -21,7 +21,6 @@ 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; @@ -34,6 +33,7 @@ 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.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; @@ -46,17 +46,21 @@ * 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) -public final class RollingRandomAccessFileAppender extends AbstractOutputStreamAppender { +@Plugin( + name = "RollingRandomAccessFile", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) +public final class RollingRandomAccessFileAppender + extends AbstractOutputStreamAppender { public static class Builder> extends AbstractOutputStreamAppender.Builder implements org.apache.logging.log4j.core.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 +104,18 @@ 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"); return null; } @@ -128,82 +133,230 @@ public RollingRandomAccessFileAppender build() { final boolean immediateFlush = isImmediateFlush(); final int bufferSize = getBufferSize(); - final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager - .getRollingRandomAccessFileManager(fileName, filePattern, append, immediateFlush, bufferSize, policy, - strategy, advertiseURI, layout, - filePermissions, fileOwner, fileGroup, getConfiguration()); + final RollingRandomAccessFileManager manager = + RollingRandomAccessFileManager.getRollingRandomAccessFileManager( + fileName, + filePattern, + append, + immediateFlush, + bufferSize, + policy, + strategy, + advertiseURI, + layout, + filePermissions, + fileOwner, + fileGroup, + getConfiguration()); if (manager == null) { return null; } manager.initialize(); - return new RollingRandomAccessFileAppender(name, layout,getFilter(), manager, fileName, filePattern, - isIgnoreExceptions(), immediateFlush, bufferSize, advertise ? getConfiguration().getAdvertiser() : null); + return new RollingRandomAccessFileAppender( + name, + layout, + getFilter(), + manager, + fileName, + filePattern, + isIgnoreExceptions(), + immediateFlush, + bufferSize, + advertise ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); + } + + /** + * @since 2.26.0 + */ + public B setFileName(final String fileName) { + this.fileName = fileName; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFilePattern(final String filePattern) { + this.filePattern = filePattern; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAppend(final boolean append) { + this.append = append; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setPolicy(final TriggeringPolicy policy) { + this.policy = policy; + return asBuilder(); } + /** + * @since 2.26.0 + */ + public B setStrategy(final RolloverStrategy strategy) { + this.strategy = strategy; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setAdvertiseURI(final String advertiseURI) { + this.advertiseURI = advertiseURI; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFilePermissions(final String filePermissions) { + this.filePermissions = filePermissions; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileOwner(final String fileOwner) { + this.fileOwner = fileOwner; + return asBuilder(); + } + + /** + * @since 2.26.0 + */ + public B setFileGroup(final String fileGroup) { + this.fileGroup = fileGroup; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setFileName(String)}. + */ + @Deprecated public B withFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFilePattern(String)}. + */ + @Deprecated public B withFilePattern(final String filePattern) { this.filePattern = filePattern; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAppend(boolean)}. + */ + @Deprecated public B withAppend(final boolean append) { this.append = append; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setPolicy(TriggeringPolicy)}. + */ + @Deprecated public B withPolicy(final TriggeringPolicy policy) { this.policy = policy; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setStrategy(RolloverStrategy)}. + */ + @Deprecated public B withStrategy(final RolloverStrategy strategy) { this.strategy = strategy; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAdvertise(boolean)}. + */ + @Deprecated public B withAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setAdvertiseURI(String)}. + */ + @Deprecated public B withAdvertiseURI(final String advertiseURI) { this.advertiseURI = advertiseURI; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFilePermissions(String)}. + */ + @Deprecated public B withFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileOwner(String)}. + */ + @Deprecated public B withFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } + /** + * @deprecated since 2.26.0 use {@link #setFileGroup(String)}. + */ + @Deprecated public B withFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } - } - + private final String fileName; private final String filePattern; private final Object advertisement; private final Advertiser advertiser; - private RollingRandomAccessFileAppender(final String name, final Layout layout, - final Filter filter, final RollingRandomAccessFileManager manager, final String fileName, - final String filePattern, final boolean ignoreExceptions, - final boolean immediateFlush, final int bufferSize, final Advertiser advertiser) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + private RollingRandomAccessFileAppender( + final String name, + final Layout layout, + final Filter filter, + final RollingRandomAccessFileManager manager, + final String fileName, + final String filePattern, + final boolean ignoreExceptions, + final boolean immediateFlush, + final int bufferSize, + 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.put("contentType", layout.getContentType()); @@ -238,14 +391,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); } @@ -328,26 +473,25 @@ public static > RollingRandomAccessFileAppender createAppen 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(); + .setAdvertise(isAdvertise) + .setAdvertiseURI(advertiseURI) + .setAppend(isAppend) + .setBufferSize(bufferSize) + .setConfiguration(configuration) + .setFileName(fileName) + .setFilePattern(filePattern) + .setFilter(filter) + .setIgnoreExceptions(isIgnoreExceptions) + .setImmediateFlush(isImmediateFlush) + .setLayout(layout) + .setName(name) + .setPolicy(policy) + .setStrategy(strategy) + .build(); } - + @PluginBuilderFactory 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 index ab9dfb48f20..9833c1ed584 100644 --- 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 @@ -1,32 +1,31 @@ /* * 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.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.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; @@ -36,7 +35,11 @@ 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) +@Plugin( + name = "ScriptAppenderSelector", + category = Core.CATEGORY_NAME, + elementType = Appender.ELEMENT_TYPE, + printObject = true) public class ScriptAppenderSelector extends AbstractAppender { /** @@ -79,12 +82,24 @@ public Appender build() { return null; } final ScriptManager scriptManager = configuration.getScriptManager(); - scriptManager.addScript(script); + if (scriptManager == null) { + LOGGER.error("Script support is not enabled"); + return null; + } + if (!scriptManager.addScript(script)) { + return null; + } 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; + LOGGER.debug( + "ScriptAppenderSelector '{}' executing {} '{}': {}", + name, + script.getLanguage(), + script.getId(), + script.getScriptText()); + final Object object = scriptManager.execute(script.getId(), bindings); + final String actualAppenderName = Objects.toString(object, null); + LOGGER.debug("ScriptAppenderSelector '{}' selected '{}'", name, actualAppenderName); + return appenderSet.createAppender(actualAppenderName, name); } public AppenderSet getAppenderSet() { @@ -103,26 +118,73 @@ public AbstractScript getScript() { return script; } - public Builder withAppenderNodeSet(@SuppressWarnings("hiding") final AppenderSet appenderSet) { + /** + * @since 2.26.0 + */ + public Builder setAppenderNodeSet(final AppenderSet appenderSet) { this.appenderSet = appenderSet; return this; } - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + /** + * @since 2.26.0 + */ + public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } - public Builder withName(@SuppressWarnings("hiding") final String name) { + /** + * @since 2.26.0 + */ + public Builder setName(final String name) { this.name = name; return this; } - public Builder withScript(@SuppressWarnings("hiding") final AbstractScript script) { + /** + * @since 2.26.0 + */ + public Builder setScript(final AbstractScript script) { this.script = script; return this; } + /** + * @deprecated since 2.26.0 use {@link #setAppenderNodeSet(AppenderSet)}. + */ + @Deprecated + public Builder withAppenderNodeSet(final AppenderSet appenderSet) { + this.appenderSet = appenderSet; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfiguration(Configuration)}. + */ + @Deprecated + public Builder withConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setName(String)}. + */ + @Deprecated + public Builder withName(final String name) { + this.name = name; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setScript(AbstractScript)}. + */ + @Deprecated + public Builder withScript(final AbstractScript script) { + this.script = script; + return this; + } } @PluginBuilderFactory @@ -130,13 +192,16 @@ public static Builder newBuilder() { return new Builder(); } - private ScriptAppenderSelector(final String name, final Filter filter, final Layout layout) { - super(name, filter, layout); + private ScriptAppenderSelector( + final String name, + final Filter filter, + final Layout layout, + final Property[] properties) { + super(name, filter, layout, true, Property.EMPTY_ARRAY); } @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 index e3aac4dbdd4..b610603b4be 100644 --- 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 @@ -1,24 +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. */ - package org.apache.logging.log4j.core.appender; import java.io.Serializable; - +import java.util.ServiceLoader; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; @@ -26,17 +25,29 @@ 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.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.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.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.AbstractStringLayout.Serializer; import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.MailManager; +import org.apache.logging.log4j.core.net.MailManager.FactoryData; +import org.apache.logging.log4j.core.net.MailManagerFactory; import org.apache.logging.log4j.core.net.SmtpManager; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.ServiceLoaderUtil; +import org.apache.logging.log4j.util.Strings; /** * Send an e-mail when a specific logging event occurs, typically on errors or @@ -62,54 +73,266 @@ public final class SmtpAppender extends AbstractAppender { private static final int DEFAULT_BUFFER_SIZE = 512; /** The SMTP Manager */ - private final SmtpManager manager; + private final MailManager manager; - private SmtpAppender(final String name, final Filter filter, final Layout layout, final SmtpManager manager, - final boolean ignoreExceptions) { - super(name, filter, layout, ignoreExceptions); + private SmtpAppender( + final String name, + final Filter filter, + final Layout layout, + final MailManager manager, + final boolean ignoreExceptions, + final Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); this.manager = manager; } + public MailManager getManager() { + return manager; + } + + /** + * @since 2.13.2 + */ + public static class Builder extends AbstractAppender.Builder + implements org.apache.logging.log4j.core.util.Builder { + @PluginBuilderAttribute + private String to; + + @PluginBuilderAttribute + private String cc; + + @PluginBuilderAttribute + private String bcc; + + @PluginBuilderAttribute + private String from; + + @PluginBuilderAttribute + private String replyTo; + + @PluginBuilderAttribute + private String subject; + + @PluginBuilderAttribute + private String smtpProtocol = "smtp"; + + @PluginBuilderAttribute + private String smtpHost; + + @PluginBuilderAttribute + @ValidPort + private int smtpPort; + + @PluginBuilderAttribute + private String smtpUsername; + + @PluginBuilderAttribute(sensitive = true) + private String smtpPassword; + + @PluginBuilderAttribute + private boolean smtpDebug; + + @PluginBuilderAttribute + private int bufferSize = DEFAULT_BUFFER_SIZE; + + @PluginElement("SSL") + private SslConfiguration sslConfiguration; + + /** + * Comma-separated list of recipient email addresses. + */ + public Builder setTo(final String to) { + this.to = to; + return this; + } + + /** + * Comma-separated list of CC email addresses. + */ + public Builder setCc(final String cc) { + this.cc = cc; + return this; + } + + /** + * Comma-separated list of BCC email addresses. + */ + public Builder setBcc(final String bcc) { + this.bcc = bcc; + return this; + } + + /** + * Email address of the sender. + */ + public Builder setFrom(final String from) { + this.from = from; + return this; + } + + /** + * Comma-separated list of Reply-To email addresses. + */ + public Builder setReplyTo(final String replyTo) { + this.replyTo = replyTo; + return this; + } + + /** + * Subject template for the email messages. + * @see org.apache.logging.log4j.core.layout.PatternLayout + */ + public Builder setSubject(final String subject) { + this.subject = subject; + return this; + } + + /** + * Transport protocol to use for SMTP such as "smtp" or "smtps". Defaults to "smtp". + */ + public Builder setSmtpProtocol(final String smtpProtocol) { + this.smtpProtocol = smtpProtocol; + return this; + } + + /** + * Host name of SMTP server to send messages through. + */ + public Builder setSmtpHost(final String smtpHost) { + this.smtpHost = smtpHost; + return this; + } + + /** + * Port number of SMTP server to send messages through. + */ + public Builder setSmtpPort(final int smtpPort) { + this.smtpPort = smtpPort; + return this; + } + + /** + * Username to authenticate with SMTP server. + */ + public Builder setSmtpUsername(final String smtpUsername) { + this.smtpUsername = smtpUsername; + return this; + } + + /** + * Password to authenticate with SMTP server. + */ + public Builder setSmtpPassword(final String smtpPassword) { + this.smtpPassword = smtpPassword; + return this; + } + + /** + * Enables or disables mail session debugging on STDOUT. Disabled by default. + */ + public Builder setSmtpDebug(final boolean smtpDebug) { + this.smtpDebug = smtpDebug; + return this; + } + + /** + * Number of log events to buffer before sending an email. Defaults to {@value #DEFAULT_BUFFER_SIZE}. + */ + public Builder setBufferSize(final int bufferSize) { + this.bufferSize = bufferSize; + return this; + } + + /** + * Specifies an SSL configuration for smtps connections. + */ + public Builder setSslConfiguration(final SslConfiguration sslConfiguration) { + this.sslConfiguration = sslConfiguration; + return this; + } + + /** + * Specifies the layout used for the email message body. By default, this uses the + * {@linkplain HtmlLayout#createDefaultLayout() default HTML layout}. + */ + @Override + public Builder setLayout(final Layout layout) { + return super.setLayout(layout); + } + + /** + * Specifies the filter used for this appender. By default, uses a {@link ThresholdFilter} with a level of + * ERROR. + */ + @Override + public Builder setFilter(final Filter filter) { + return super.setFilter(filter); + } + + @Override + public SmtpAppender build() { + if (getLayout() == null) { + setLayout(HtmlLayout.createDefaultLayout()); + } + if (getFilter() == null) { + setFilter(ThresholdFilter.createFilter(null, null, null)); + } + if (Strings.isEmpty(smtpProtocol)) { + smtpProtocol = "smtp"; + } + final Serializer subjectSerializer = PatternLayout.newSerializerBuilder() + .setConfiguration(getConfiguration()) + .setPattern(subject) + .build(); + final FactoryData data = new FactoryData( + to, + cc, + bcc, + from, + replyTo, + subject, + subjectSerializer, + smtpProtocol, + smtpHost, + smtpPort, + smtpUsername, + smtpPassword, + smtpDebug, + bufferSize, + sslConfiguration, + getFilter().toString()); + final MailManagerFactory factory = ServiceLoaderUtil.safeStream( + MailManagerFactory.class, + ServiceLoader.load( + MailManagerFactory.class, getClass().getClassLoader()), + StatusLogger.getLogger()) + .findAny() + .orElseGet(() -> SmtpManager.FACTORY); + final MailManager smtpManager = AbstractManager.getManager(data.getManagerName(), factory, data); + if (smtpManager == null) { + LOGGER.error("Unabled to instantiate SmtpAppender named {}", getName()); + return null; + } + + return new SmtpAppender( + getName(), getFilter(), getLayout(), smtpManager, isIgnoreExceptions(), getPropertyArray()); + } + } + + /** + * @since 2.13.2 + */ + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + /** * 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. + * @deprecated Use {@link #newBuilder()} to create and configure a {@link Builder} instance. + * @see Builder */ - @PluginFactory + @Deprecated public static SmtpAppender createAppender( @PluginConfiguration final Configuration config, @PluginAttribute("name") @Required final String name, @@ -133,27 +356,15 @@ public static SmtpAppender createAppender( 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); + return SmtpAppender.newBuilder() + .setIgnoreExceptions(Booleans.parseBoolean(ignore, true)) + .setSmtpPort(AbstractAppender.parseInt(smtpPortStr, 0)) + .setSmtpDebug(Boolean.parseBoolean(smtpDebug)) + .setBufferSize(bufferSizeStr == null ? DEFAULT_BUFFER_SIZE : Integers.parseInt(bufferSizeStr)) + .setLayout(layout) + .setFilter(filter) + .setConfiguration(config != null ? config : new DefaultConfiguration()) + .build(); } /** 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..13c918d9b02 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender; @@ -20,7 +20,6 @@ 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; @@ -28,6 +27,7 @@ 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.Property; 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; @@ -54,21 +54,22 @@ public class SocketAppender extends AbstractOutputStreamAppenderDefaults + *

Defaults

*
    *
  • host: "localhost"
  • *
  • protocol: "TCP"
  • *
- *

Changes

+ *

Changes

*
    *
  • Removed deprecated "delayMillis", use "reconnectionDelayMillis".
  • *
  • Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".
  • *
- * + * * @param * The type to build. */ - public static abstract class AbstractBuilder> extends AbstractOutputStreamAppender.Builder { + public abstract static class AbstractBuilder> + extends AbstractOutputStreamAppender.Builder { @PluginBuilderAttribute private boolean advertise; @@ -91,14 +92,14 @@ public static abstract class AbstractBuilder> exten private Protocol protocol = Protocol.TCP; @PluginBuilderAttribute - @PluginAliases({ "reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis" }) + @PluginAliases({"reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis"}) private int reconnectDelayMillis; - + @PluginElement("SocketOptions") private SocketOptions socketOptions; - + @PluginElement("SslConfiguration") - @PluginAliases({ "SslConfig" }) + @PluginAliases({"SslConfig"}) private SslConfiguration sslConfiguration; public boolean getAdvertise() { @@ -129,46 +130,100 @@ public boolean getImmediateFail() { return immediateFail; } + public B setAdvertise(final boolean advertise) { + this.advertise = advertise; + return asBuilder(); + } + + public B setConnectTimeoutMillis(final int connectTimeoutMillis) { + this.connectTimeoutMillis = connectTimeoutMillis; + return asBuilder(); + } + + public B setHost(final String host) { + this.host = host; + return asBuilder(); + } + + public B setImmediateFail(final boolean immediateFail) { + this.immediateFail = immediateFail; + return asBuilder(); + } + + public B setPort(final int port) { + this.port = port; + return asBuilder(); + } + + public B setProtocol(final Protocol protocol) { + this.protocol = protocol; + return asBuilder(); + } + + public B setReconnectDelayMillis(final int reconnectDelayMillis) { + this.reconnectDelayMillis = reconnectDelayMillis; + return asBuilder(); + } + + public B setSocketOptions(final SocketOptions socketOptions) { + this.socketOptions = socketOptions; + return asBuilder(); + } + + public B setSslConfiguration(final SslConfiguration sslConfiguration) { + this.sslConfiguration = sslConfiguration; + return asBuilder(); + } + + @Deprecated public B withAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } + @Deprecated public B withConnectTimeoutMillis(final int connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; return asBuilder(); } + @Deprecated public B withHost(final String host) { this.host = host; return asBuilder(); } + @Deprecated public B withImmediateFail(final boolean immediateFail) { this.immediateFail = immediateFail; return asBuilder(); } + @Deprecated public B withPort(final int port) { this.port = port; return asBuilder(); } + @Deprecated public B withProtocol(final Protocol protocol) { this.protocol = protocol; return asBuilder(); } + @Deprecated public B withReconnectDelayMillis(final int reconnectDelayMillis) { this.reconnectDelayMillis = reconnectDelayMillis; return asBuilder(); } + @Deprecated public B withSocketOptions(final SocketOptions socketOptions) { this.socketOptions = socketOptions; return asBuilder(); } + @Deprecated public B withSslConfiguration(final SslConfiguration sslConfiguration) { this.sslConfiguration = sslConfiguration; return asBuilder(); @@ -181,15 +236,14 @@ public int getReconnectDelayMillis() { public SocketOptions getSocketOptions() { return socketOptions; } - } - + /** * 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 { @@ -217,14 +271,31 @@ public SocketAppender build() { immediateFlush = true; } - final AbstractSocketManager manager = SocketAppender.createSocketManager(name, actualProtocol, getHost(), getPort(), - getConnectTimeoutMillis(), getSslConfiguration(), getReconnectDelayMillis(), getImmediateFail(), layout, getBufferSize(), getSocketOptions()); - - return new SocketAppender(name, layout, getFilter(), manager, isIgnoreExceptions(), - !bufferedIo || immediateFlush, getAdvertise() ? getConfiguration().getAdvertiser() : null); + final AbstractSocketManager manager = SocketAppender.createSocketManager( + name, + actualProtocol, + getHost(), + getPort(), + getConnectTimeoutMillis(), + getSslConfiguration(), + getReconnectDelayMillis(), + getImmediateFail(), + layout, + getBufferSize(), + getSocketOptions()); + + return new SocketAppender( + name, + layout, + getFilter(), + manager, + isIgnoreExceptions(), + !bufferedIo || immediateFlush, + getAdvertise() ? getConfiguration().getAdvertiser() : null, + getPropertyArray()); } } - + @PluginBuilderFactory public static Builder newBuilder() { return new Builder(); @@ -233,10 +304,16 @@ public static Builder newBuilder() { private final Object advertisement; private final Advertiser advertiser; - 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); + protected SocketAppender( + final String name, + final Layout layout, + final Filter filter, + final AbstractSocketManager 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()); @@ -249,6 +326,21 @@ protected SocketAppender(final String name, final Layout this.advertiser = advertiser; } + /** + * @deprecated {@link #SocketAppender(String, Layout, Filter, AbstractSocketManager, boolean, boolean, Advertiser, Property[])}. + */ + @Deprecated + protected SocketAppender( + final String name, + final Layout layout, + final Filter filter, + final AbstractSocketManager manager, + final boolean ignoreExceptions, + final boolean immediateFlush, + final Advertiser advertiser) { + this(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser, Property.EMPTY_ARRAY); + } + @Override public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); @@ -313,27 +405,27 @@ public static SocketAppender createAppender( final Filter filter, final boolean advertise, final Configuration configuration) { - // @formatter:on + // @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(); + .setAdvertise(advertise) + .setConfiguration(configuration) + .setConnectTimeoutMillis(connectTimeoutMillis) + .setFilter(filter) + .setHost(host) + .setIgnoreExceptions(ignoreExceptions) + .setImmediateFail(immediateFail) + .setLayout(layout) + .setName(name) + .setPort(port) + .setProtocol(protocol) + .setReconnectDelayMillis(reconnectDelayMillis) + .setSslConfiguration(sslConfig) + .build(); // @formatter:on } - + /** * Creates a socket appender. * @@ -387,7 +479,7 @@ public static SocketAppender createAppender( final Filter filter, final String advertise, final Configuration config) { - // @formatter:on + // @formatter:on final boolean isFlush = Booleans.parseBoolean(immediateFlush, true); final boolean isAdvertise = Boolean.parseBoolean(advertise); final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); @@ -395,8 +487,21 @@ public static SocketAppender createAppender( 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); + return createAppender( + host, + port, + p, + sslConfig, + connectTimeoutMillis, + reconnectDelayMillis, + fail, + name, + isFlush, + ignoreExceptions, + layout, + filter, + isAdvertise, + config); } /** @@ -407,10 +512,29 @@ public static SocketAppender createAppender( * @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); + 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); } /** @@ -419,10 +543,18 @@ protected static AbstractSocketManager createSocketManager(final String name, fi * @throws IllegalArgumentException * if the protocol cannot be handled. */ - protected static AbstractSocketManager createSocketManager(final String name, 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, final SocketOptions socketOptions) { + protected static AbstractSocketManager createSocketManager( + final String name, + 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, + final SocketOptions socketOptions) { if (protocol == Protocol.TCP && sslConfig != null) { // Upgrade TCP to SSL if an SSL config is specified. protocol = Protocol.SSL; @@ -431,16 +563,31 @@ protected static AbstractSocketManager createSocketManager(final String name, Pr LOGGER.info("Appender {} ignoring SSL configuration for {} protocol", name, protocol); } switch (protocol) { - case TCP: - return TcpSocketManager.getSocketManager(host, port, connectTimeoutMillis, reconnectDelayMillis, - immediateFail, layout, bufferSize, socketOptions); - case UDP: - return DatagramSocketManager.getSocketManager(host, port, layout, bufferSize); - case SSL: - return SslSocketManager.getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis, - immediateFail, layout, bufferSize, socketOptions); - default: - throw new IllegalArgumentException(protocol.toString()); + case TCP: + return TcpSocketManager.getSocketManager( + host, + port, + connectTimeoutMillis, + reconnectDelayMillis, + immediateFail, + layout, + bufferSize, + socketOptions); + case UDP: + return DatagramSocketManager.getSocketManager(host, port, layout, bufferSize); + case SSL: + return SslSocketManager.getSocketManager( + sslConfig, + host, + port, + connectTimeoutMillis, + reconnectDelayMillis, + immediateFail, + layout, + bufferSize, + socketOptions); + default: + throw new IllegalArgumentException(protocol.toString()); } } 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..21af109bd28 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 @@ -1,30 +1,30 @@ /* * 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.logging.log4j.core.appender; import java.io.Serializable; import java.nio.charset.Charset; 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.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; @@ -54,56 +54,56 @@ public static class Builder> extends AbstractBuilder @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,29 +113,63 @@ 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) + .setConfiguration(configuration) + .build() : // @formatter:off SyslogLayout.newBuilder() - .setFacility(facility) - .setIncludeNewLine(newLine) - .setEscapeNL(escapeNL) - .setCharset(charsetName) - .build(); - // @formatter:off + .setFacility(facility) + .setIncludeNewLine(newLine) + .setEscapeNL(escapeNL) + .setCharset(charsetName) + .build(); + // @formatter:on } final String name = getName(); if (name == null) { LOGGER.error("No name provided for SyslogAppender"); return null; } - final AbstractSocketManager manager = createSocketManager(name, protocol, getHost(), getPort(), getConnectTimeoutMillis(), - sslConfiguration, getReconnectDelayMillis(), getImmediateFail(), layout, Constants.ENCODER_BYTE_BUFFER_SIZE, null); - - return new SyslogAppender(name, layout, getFilter(), isIgnoreExceptions(), isImmediateFlush(), manager, - getAdvertise() ? configuration.getAdvertiser() : null); + final AbstractSocketManager manager = createSocketManager( + name, + protocol, + getHost(), + getPort(), + getConnectTimeoutMillis(), + sslConfiguration, + getReconnectDelayMillis(), + getImmediateFail(), + layout, + Constants.ENCODER_BYTE_BUFFER_SIZE, + getSocketOptions()); + + return new SyslogAppender( + name, + layout, + getFilter(), + isIgnoreExceptions(), + isImmediateFlush(), + manager, + getAdvertise() ? configuration.getAdvertiser() : null, + null); } public Facility getFacility() { @@ -146,7 +180,7 @@ public String getId() { return id; } - public int getEnterpriseNumber() { + public String getEnterpriseNumber() { return enterpriseNumber; } @@ -220,11 +254,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,14 +342,35 @@ 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, - final boolean ignoreExceptions, final boolean immediateFlush, - final AbstractSocketManager manager, final Advertiser advertiser) { - super(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser); + protected SyslogAppender( + final String name, + final Layout layout, + final Filter filter, + final boolean ignoreExceptions, + final boolean immediateFlush, + final AbstractSocketManager manager, + final Advertiser advertiser, + final Property[] properties) { + super(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser, properties); + } + /** + * @deprecated Use + * {@link #SyslogAppender(String, Layout, Filter, boolean, boolean, AbstractSocketManager, Advertiser, Property[])}. + */ + @Deprecated + protected SyslogAppender( + final String name, + final Layout layout, + final Filter filter, + final boolean ignoreExceptions, + final boolean immediateFlush, + final AbstractSocketManager manager, + final Advertiser advertiser) { + super(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser, Property.EMPTY_ARRAY); } /** @@ -381,25 +444,25 @@ public static > SyslogAppender createAppender( final Configuration configuration, final Charset charset, final String exceptionPattern, - final LoggerFields[] loggerFields, + 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) + .setHost(host) + .setPort(port) + .setProtocol(EnglishEnums.valueOf(Protocol.class, protocolStr)) + .setSslConfiguration(sslConfiguration) + .setConnectTimeoutMillis(connectTimeoutMillis) + .setReconnectDelayMillis(reconnectDelayMillis) + .setImmediateFail(immediateFail) + .setName(appName) + .setImmediateFlush(immediateFlush) + .setIgnoreExceptions(ignoreExceptions) + .setFilter(filter) .setConfiguration(configuration) - .withAdvertise(advertise) + .setAdvertise(advertise) .setFacility(facility) .setId(id) .setEnterpriseNumber(enterpriseNumber) @@ -420,11 +483,10 @@ public static > SyslogAppender createAppender( .build(); // @formatter:on } - + // Calling this method newBuilder() does not compile @PluginBuilderFactory 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..3dc26d8086f 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 @@ -1,29 +1,29 @@ /* * 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.logging.log4j.core.appender; import java.nio.charset.StandardCharsets; - +import java.util.Objects; import org.apache.logging.log4j.util.Chars; /** * Wraps messages that are formatted according to RFC 5425. - * - * @see RFC 5425 + * + * @see RFC 5425 */ public class TlsSyslogFrame { private final String message; @@ -46,10 +46,7 @@ public String toString() { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((message == null) ? 0 : message.hashCode()); - return result; + return 31 + Objects.hashCode(message); } @Override @@ -64,14 +61,9 @@ public boolean equals(final Object obj) { return false; } final TlsSyslogFrame other = (TlsSyslogFrame) obj; - if (message == null) { - if (other.message != null) { - return false; - } - } else if (!message.equals(other.message)) { + if (!Objects.equals(message, other.message)) { return false; } return true; } - } 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..704822c402f 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 @@ -1,27 +1,29 @@ /* * 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.logging.log4j.core.appender; +import java.io.Serializable; 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.Layout; import org.apache.logging.log4j.core.StringLayout; +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.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginFactory; @@ -37,53 +39,38 @@ 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.core.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); - } - - public Builder setFilter(final Filter aFilter) { - this.filter = aFilter; - return this; + final Layout layout = getOrCreateLayout(); + if (!(layout instanceof StringLayout)) { + LOGGER.error("Layout must be a StringLayout to log to ServletContext"); + return null; + } + final StringLayout stringLayout = (StringLayout) layout; + return new WriterAppender( + getName(), + stringLayout, + getFilter(), + getManager(target, follow, stringLayout), + isIgnoreExceptions(), + getPropertyArray()); } - public Builder setFollow(final boolean shouldFollow) { + public B setFollow(final boolean shouldFollow) { this.follow = shouldFollow; - return this; + return asBuilder(); } - 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; - } - - public Builder setTarget(final Writer aTarget) { + public B setTarget(final Writer aTarget) { this.target = aTarget; - return this; + return asBuilder(); } } /** @@ -96,7 +83,7 @@ private static class FactoryData { /** * Builds instances. - * + * * @param writer * The OutputStream. * @param type @@ -115,7 +102,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); + private WriterAppender( + final String name, + final StringLayout layout, + final Filter filter, + 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/WriterManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java index bf76e47b25a..7fe3a3001f8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterManager.java @@ -1,149 +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 java.io.IOException; -import java.io.Writer; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.StringLayout; - -/** - * Manages a Writer so that it can be shared by multiple Appenders and will - * allow appenders to reconfigure without requiring a new writer. - */ -public class WriterManager extends AbstractManager { - - /** - * Creates a Manager. - * - * @param name The name of the stream to manage. - * @param data The data to pass to the Manager. - * @param factory The factory to use to create the Manager. - * @param The type of the WriterManager. - * @return A WriterManager. - */ - public static WriterManager getManager(final String name, final T data, - final ManagerFactory factory) { - return AbstractManager.getManager(name, factory, data); - } - protected final StringLayout layout; - - private volatile Writer writer; - - public WriterManager(final Writer writer, final String streamName, final StringLayout layout, - final boolean writeHeader) { - super(null, streamName); - this.writer = writer; - this.layout = layout; - if (writeHeader && layout != null) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - this.writer.write(new String(header, layout.getCharset())); - } catch (final IOException e) { - logError("Unable to write header", e); - } - } - } - } - - protected synchronized void closeWriter() { - final Writer w = writer; // access volatile field only once per method - try { - w.close(); - } catch (final IOException ex) { - logError("Unable to close stream", ex); - } - } - - /** - * Flushes any buffers. - */ - public synchronized void flush() { - try { - writer.flush(); - } catch (final IOException ex) { - final String msg = "Error flushing stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - protected Writer getWriter() { - return writer; - } - - /** - * Returns the status of the stream. - * @return true if the stream is open, false if it is not. - */ - public boolean isOpen() { - return getCount() > 0; - } - - /** - * Default hook to write footer during close. - */ - @Override - public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - writeFooter(); - closeWriter(); - return true; - } - - protected void setWriter(final Writer writer) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - writer.write(new String(header, layout.getCharset())); - this.writer = writer; // only update field if writer.write() succeeded - } catch (final IOException ioe) { - logError("Unable to write header", ioe); - } - } else { - this.writer = writer; - } - } - - /** - * Some output streams synchronize writes while others do not. Synchronizing here insures that - * log events won't be intertwined. - * @param str the string to write - * @throws AppenderLoggingException if an error occurs. - */ - protected synchronized void write(final String str) { - try { - writer.write(str); - } catch (final IOException ex) { - final String msg = "Error writing to stream " + getName(); - throw new AppenderLoggingException(msg, ex); - } - } - - /** - * Writes the footer. - */ - protected void writeFooter() { - if (layout == null) { - return; - } - final byte[] footer = layout.getFooter(); - if (footer != null && footer.length > 0) { - write(new String(footer, layout.getCharset())); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.io.Writer; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.StringLayout; + +/** + * Manages a Writer so that it can be shared by multiple Appenders and will + * allow appenders to reconfigure without requiring a new writer. + */ +public class WriterManager extends AbstractManager { + + /** + * Creates a Manager. + * + * @param name The name of the stream to manage. + * @param data The data to pass to the Manager. + * @param factory The factory to use to create the Manager. + * @param The type of the WriterManager. + * @return A WriterManager. + */ + public static WriterManager getManager( + final String name, final T data, final ManagerFactory factory) { + return AbstractManager.getManager(name, factory, data); + } + + protected final StringLayout layout; + + private volatile Writer writer; + + public WriterManager( + final Writer writer, final String streamName, final StringLayout layout, final boolean writeHeader) { + super(null, streamName); + this.writer = writer; + this.layout = layout; + if (writeHeader && layout != null) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + this.writer.write(new String(header, layout.getCharset())); + } catch (final IOException e) { + logError("Unable to write header", e); + } + } + } + } + + protected synchronized void closeWriter() { + final Writer w = writer; // access volatile field only once per method + try { + w.close(); + } catch (final IOException ex) { + logError("Unable to close stream", ex); + } + } + + /** + * Flushes any buffers. + */ + public synchronized void flush() { + try { + writer.flush(); + } catch (final IOException ex) { + final String msg = "Error flushing stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + protected Writer getWriter() { + return writer; + } + + /** + * Returns the status of the stream. + * @return true if the stream is open, false if it is not. + */ + public boolean isOpen() { + return getCount() > 0; + } + + /** + * Default hook to write footer during close. + */ + @Override + public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + writeFooter(); + closeWriter(); + return true; + } + + protected void setWriter(final Writer writer) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + writer.write(new String(header, layout.getCharset())); + this.writer = writer; // only update field if writer.write() succeeded + } catch (final IOException ioe) { + logError("Unable to write header", ioe); + } + } else { + this.writer = writer; + } + } + + /** + * Some output streams synchronize writes while others do not. Synchronizing here insures that + * log events won't be intertwined. + * @param str the string to write + * @throws AppenderLoggingException if an error occurs. + */ + protected synchronized void write(final String str) { + try { + writer.write(str); + } catch (final IOException ex) { + final String msg = "Error writing to stream " + getName(); + throw new AppenderLoggingException(msg, ex); + } + } + + /** + * Writes the footer. + */ + protected void writeFooter() { + if (layout == null) { + return; + } + final byte[] footer = layout.getFooter(); + if (footer != null && footer.length > 0) { + write(new String(footer, layout.getCharset())); + } + } +} 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..ecfb1061ada 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db; @@ -21,28 +21,31 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - import org.apache.logging.log4j.LoggingException; 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.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 - * should inherit from this base appender. Three implementations are currently provided: - * {@link org.apache.logging.log4j.core.appender.db.jdbc JDBC}, {@link org.apache.logging.log4j.core.appender.db.jpa - * JPA}, and {@link org.apache.logging.log4j.core.appender.nosql NoSQL}. + * should inherit from this base appender. * * @param Specifies which type of {@link AbstractDatabaseManager} this Appender requires. */ 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; /** @@ -53,10 +56,33 @@ public abstract class AbstractDatabaseAppender layout, + final boolean ignoreExceptions, + final Property[] properties, + final T manager) { + super(name, filter, layout, ignoreExceptions, properties); this.manager = manager; } @@ -69,13 +95,43 @@ protected AbstractDatabaseAppender(final String name, final Filter filter, final * @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. + * @deprecated Use {@link #AbstractDatabaseAppender(String, Filter, Layout, boolean, Property[], AbstractDatabaseManager)} */ - protected AbstractDatabaseAppender(final String name, final Filter filter, - final Layout layout, final boolean ignoreExceptions, final T manager) { - super(name, filter, layout, ignoreExceptions); + @Deprecated + protected AbstractDatabaseAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean ignoreExceptions, + final T manager) { + super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); 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 +152,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 +194,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..f166ec2ce88 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 @@ -1,99 +1,222 @@ /* * 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.logging.log4j.core.appender.db; import java.io.Flushable; import java.io.Serializable; import java.util.ArrayList; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractManager; import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.config.Configuration; /** * 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 extends AbstractManager.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 + * @deprecated Use {@link AbstractFactoryData#AbstractFactoryData(Configuration, int, Layout)}. + */ + protected AbstractFactoryData(final int bufferSize, final Layout layout) { + this(null, bufferSize, layout); + } + + /** + * Constructs the base factory data. + * @param configuration Configuration creating this instance. + * @param bufferSize The size of the buffer. + * @param layout The appender-level layout + */ + protected AbstractFactoryData( + final Configuration configuration, final int bufferSize, final Layout layout) { + super(configuration); + 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. + * Constructs the base manager. * * @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 bufferSize The size of the log event buffer. + * @deprecated Use {@link AbstractDatabaseManager#AbstractDatabaseManager(String, int, Layout, Configuration)}. */ + @Deprecated protected AbstractDatabaseManager(final String name, final int bufferSize) { this(name, bufferSize, null); } /** - * Instantiates the base manager. + * Constructs the base manager. + * + * @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 layout the Appender-level layout. + * @param bufferSize The size of the log event buffer. + * @deprecated Use {@link AbstractDatabaseManager#AbstractDatabaseManager(String, int, Layout, Configuration)}. + */ + @Deprecated + protected AbstractDatabaseManager( + final String name, final int bufferSize, final Layout layout) { + this(name, bufferSize, layout, null); + } + + /** + * Constructs the base manager. * * @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 layout the Appender-level layout. * @param bufferSize The size of the log event buffer. + * @param configuration My configuration. */ - protected AbstractDatabaseManager(final String name, final int bufferSize, final Layout layout) { - super(null, name); + protected AbstractDatabaseManager( + final String name, + final int bufferSize, + final Layout layout, + final Configuration configuration) { + // null configuration allowed for backward compatibility. + // TODO should super track Configuration instead of LoggerContext? + super(configuration != null ? configuration.getLoggerContext() : null, name); this.bufferSize = bufferSize; this.buffer = new ArrayList<>(bufferSize + 1); - this.layout = layout; + this.layout = layout; // A null layout is allowed. + } + + 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,74 +241,47 @@ 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)}. + * 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. */ - @Deprecated - protected abstract void writeInternal(LogEvent event); + protected abstract boolean shutdownInternal() throws Exception; /** - * 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. + * 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. */ - protected abstract void writeInternal(LogEvent event, Serializable serializable); + public final synchronized void startup() { + if (!this.isRunning()) { + try { + this.startupInternal(); + this.running = true; + } catch (final Exception e) { + logError("Could not perform database startup operations", e); + } + } + } /** - * 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. + * 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. */ - protected abstract boolean commitAndClose(); + protected abstract void startupInternal() 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. - */ @Override - public final synchronized void flush() { - if (this.isRunning() && this.buffer.size() > 0) { - this.connectAndStart(); - 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(); - } - } + public final String toString() { + return this.getName(); } /** * 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)}. + * @deprecated since 2.11.0 Use {@link #write(LogEvent, Serializable)}. */ @Deprecated public final synchronized void write(final LogEvent event) { @@ -199,84 +295,39 @@ 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. + * @param event The event to write to the database. + * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}. */ - protected static M getManager( - final String name, final T data, final ManagerFactory factory - ) { - return AbstractManager.getManager(name, factory, data); + @Deprecated + protected void writeInternal(final LogEvent event) { + writeInternal(event, null); } /** - * Implementations should extend this class for passing data between the getManager method and the manager factory - * class. + * 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 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..4982c5b05c2 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 @@ -1,23 +1,25 @@ /* * 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.logging.log4j.core.appender.db; -import java.util.Date; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; +import java.util.Date; +import java.util.Objects; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.StringLayout; @@ -42,6 +44,11 @@ @Plugin(name = "ColumnMapping", category = Core.CATEGORY_NAME, printObject = true) public class ColumnMapping { + /** + * The empty array. + */ + public static final ColumnMapping[] EMPTY_ARRAY = {}; + /** * Builder for {@link ColumnMapping}. */ @@ -69,32 +76,42 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @PluginBuilderAttribute private String source; + @PluginBuilderAttribute + @Deprecated + private Class type; + @PluginBuilderAttribute @Required(message = "No conversion type provided") - private Class type = String.class; + private Class columnType = String.class; @Override public ColumnMapping build() { if (pattern != null) { layout = PatternLayout.newBuilder() - .withPattern(pattern) - .withConfiguration(configuration) - .build(); + .setPattern(pattern) + .setConfiguration(configuration) + .setAlwaysWriteExceptions(false) + .build(); } + final Class columnType = type != null ? type : this.columnType; if (!(layout == null - || literal == null - || Date.class.isAssignableFrom(type) - || ReadOnlyStringMap.class.isAssignableFrom(type) - || ThreadContextMap.class.isAssignableFrom(type) - || ThreadContextStack.class.isAssignableFrom(type))) { - LOGGER.error("No 'layout' or 'literal' value specified and type ({}) is not compatible with ThreadContextMap, ThreadContextStack, or java.util.Date for the mapping", type, this); + || literal == null + || Date.class.isAssignableFrom(columnType) + || ReadOnlyStringMap.class.isAssignableFrom(columnType) + || ThreadContextMap.class.isAssignableFrom(columnType) + || ThreadContextStack.class.isAssignableFrom(columnType))) { + LOGGER.error( + "No 'layout' or 'literal' value specified and type ({}) is not compatible with " + + "ThreadContextMap, ThreadContextStack, or java.util.Date for the mapping", + columnType, + this); return null; } if (literal != null && parameter != null) { 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, columnType); } public Builder setConfiguration(final Configuration configuration) { @@ -105,8 +122,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 +133,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 +143,8 @@ public Builder setLiteral(final String literal) { /** * Column name. - * - * @return this. + * + * @return this. */ public Builder setName(final String name) { this.name = name; @@ -137,19 +154,19 @@ 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; + this.parameter = parameter; return this; } /** * 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 +176,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,9 +188,18 @@ 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 setColumnType(final Class columnType) { + this.columnType = columnType; + return this; + } + + /** + * @see Builder#setColumnType(Class) + */ + @Deprecated public Builder setType(final Class type) { this.type = type; return this; @@ -182,25 +208,38 @@ public Builder setType(final Class type) { @Override public String toString() { return "Builder [name=" + name + ", source=" + source + ", literal=" + literal + ", parameter=" + parameter - + ", pattern=" + pattern + ", type=" + type + ", layout=" + layout + "]"; + + ", pattern=" + pattern + ", columnType=" + columnType + ", layout=" + layout + "]"; } } private static final Logger LOGGER = StatusLogger.getLogger(); + @PluginBuilderFactory public static Builder newBuilder() { return new Builder(); } - + + public static String toKey(final String name) { + return toRootUpperCase(name); + } + 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 ColumnMapping(final String name, final String source, final StringLayout layout, final String literalValue, final String parameter, final Class type) { - this.name = name; + private ColumnMapping( + final String name, + final String source, + final StringLayout layout, + final String literalValue, + final String parameter, + final Class type) { + this.name = Objects.requireNonNull(name); + this.nameKey = toKey(name); this.source = source; this.layout = layout; this.literalValue = literalValue; @@ -220,6 +259,10 @@ public String getName() { return name; } + public String getNameKey() { + return nameKey; + } + public String getParameter() { return parameter; } @@ -238,4 +281,21 @@ public String toString() { + parameter + ", type=" + type + ", layout=" + layout + "]"; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ColumnMapping that = (ColumnMapping) o; + return Objects.equals(layout, that.layout) + && Objects.equals(literalValue, that.literalValue) + && name.equals(that.name) + && Objects.equals(parameter, that.parameter) + && Objects.equals(source, that.source) + && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(layout, literalValue, name, parameter, source, type); + } } 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..bdeb62cad96 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.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.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/AbstractConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractConnectionSource.java index bd04eac95cc..fe638a7ab71 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractConnectionSource.java @@ -1,26 +1,25 @@ /* * 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.logging.log4j.core.appender.db.jdbc; import org.apache.logging.log4j.core.AbstractLifeCycle; public abstract class AbstractConnectionSource extends AbstractLifeCycle implements ConnectionSource { - + // nothing yet } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java index 77c4391b4e4..1facaa157a4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db.jdbc; @@ -20,7 +20,6 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; @@ -48,19 +47,19 @@ public static class Builder> { @PluginBuilderAttribute @Required - private String connectionString; + protected String connectionString; @PluginBuilderAttribute - private String driverClassName; + protected String driverClassName; @PluginBuilderAttribute - private char[] password; + protected char[] password; @PluginElement("Properties") - private Property[] properties; + protected Property[] properties; @PluginBuilderAttribute - private char[] userName; + protected char[] userName; @SuppressWarnings("unchecked") protected B asBuilder() { @@ -126,9 +125,13 @@ public static Logger getLogger() { private final Property[] properties; private final char[] userName; - public AbstractDriverManagerConnectionSource(final String driverClassName, final String connectionString, - String actualConnectionString, final char[] userName, final char[] password, final Property[] properties) { - super(); + public AbstractDriverManagerConnectionSource( + final String driverClassName, + final String connectionString, + final String actualConnectionString, + final char[] userName, + final char[] password, + final Property[] properties) { this.driverClassName = driverClassName; this.connectionString = connectionString; this.actualConnectionString = actualConnectionString; @@ -156,8 +159,13 @@ public Connection getConnection() throws SQLException { } else { connection = DriverManager.getConnection(actualConnectionString, toString(userName), toString(password)); } - LOGGER.debug("{} acquired connection for '{}': {} ({}@{})", getClass().getSimpleName(), actualConnectionString, - connection, connection.getClass().getName(), Integer.toHexString(connection.hashCode())); + LOGGER.debug( + "{} acquired connection for '{}': {} ({}@{})", + getClass().getSimpleName(), + actualConnectionString, + connection, + connection.getClass().getName(), + Integer.toHexString(connection.hashCode())); return connection; } @@ -200,13 +208,16 @@ protected void loadDriver(final String className) throws SQLException { try { Class.forName(className); } catch (final Exception e) { - throw new SQLException(String.format("The %s could not load the JDBC driver %s: %s", - getClass().getSimpleName(), className, e.toString()), e); + throw new SQLException( + String.format( + "The %s could not load the JDBC driver %s: %s", + getClass().getSimpleName(), className, e.toString()), + e); } } } - private Properties toProperties(final Property[] properties) { + protected Properties toProperties(final Property[] properties) { final Properties props = new Properties(); for (final Property property : properties) { props.setProperty(property.getName(), property.getValue()); @@ -219,7 +230,7 @@ public String toString() { return this.connectionString; } - private String toString(final char[] value) { + protected String toString(final char[] value) { return value == null ? null : String.valueOf(value); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java index 1377b2a49f3..9778ecfa6a1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ColumnConfig.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db.jdbc; @@ -37,90 +37,6 @@ */ @Plugin(name = "Column", category = Core.CATEGORY_NAME, printObject = true) public final class ColumnConfig { - private static final Logger LOGGER = StatusLogger.getLogger(); - - private final String columnName; - private final PatternLayout layout; - private final String literalValue; - private final boolean eventTimestamp; - private final boolean unicode; - private final boolean clob; - - private ColumnConfig(final String columnName, final PatternLayout layout, final String literalValue, - final boolean eventDate, final boolean unicode, final boolean clob) { - this.columnName = columnName; - this.layout = layout; - this.literalValue = literalValue; - this.eventTimestamp = eventDate; - this.unicode = unicode; - this.clob = clob; - } - - public String getColumnName() { - return this.columnName; - } - - public PatternLayout getLayout() { - return this.layout; - } - - public String getLiteralValue() { - return this.literalValue; - } - - public boolean isEventTimestamp() { - return this.eventTimestamp; - } - - public boolean isUnicode() { - return this.unicode; - } - - public boolean isClob() { - return this.clob; - } - - @Override - public String toString() { - return "{ name=" + this.columnName + ", layout=" + this.layout + ", literal=" + this.literalValue - + ", timestamp=" + this.eventTimestamp + " }"; - } - - /** - * Factory method for creating a column config within the plugin manager. - * - * @see Builder - * @deprecated use {@link #newBuilder()} - */ - @Deprecated - public static ColumnConfig createColumnConfig(final Configuration config, final String name, final String pattern, - final String literalValue, final String eventTimestamp, - final String unicode, final String clob) { - if (Strings.isEmpty(name)) { - LOGGER.error("The column config is not valid because it does not contain a column name."); - return null; - } - - final boolean isEventTimestamp = Boolean.parseBoolean(eventTimestamp); - final boolean isUnicode = Booleans.parseBoolean(unicode, true); - final boolean isClob = Boolean.parseBoolean(clob); - - return newBuilder() - .setConfiguration(config) - .setName(name) - .setPattern(pattern) - .setLiteral(literalValue) - .setEventTimestamp(isEventTimestamp) - .setUnicode(isUnicode) - .setClob(isClob) - .build(); - } - - @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); - } - public static class Builder implements org.apache.logging.log4j.core.util.Builder { @PluginConfiguration @@ -145,42 +61,80 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @PluginBuilderAttribute private boolean isClob; + @Override + public ColumnConfig build() { + if (Strings.isEmpty(name)) { + LOGGER.error("The column config is not valid because it does not contain a column name."); + return null; + } + + final boolean isPattern = Strings.isNotEmpty(pattern); + final boolean isLiteralValue = Strings.isNotEmpty(literal); + + if ((isPattern && isLiteralValue) + || (isPattern && isEventTimestamp) + || (isLiteralValue && isEventTimestamp)) { + LOGGER.error("The pattern, literal, and isEventTimestamp attributes are mutually exclusive."); + return null; + } + + if (isEventTimestamp) { + return new ColumnConfig(name, null, null, true, false, false); + } + + if (isLiteralValue) { + return new ColumnConfig(name, null, literal, false, false, false); + } + + if (isPattern) { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern(pattern) + .setConfiguration(configuration) + .setAlwaysWriteExceptions(false) + .build(); + return new ColumnConfig(name, layout, null, false, isUnicode, isClob); + } + + LOGGER.error("To configure a column you must specify a pattern or literal or set isEventDate to true."); + return null; + } + /** - * The configuration object. - * - * @return this. + * If {@code "true"}, indicates that the column is a character LOB (CLOB). + * + * @return this. */ - public Builder setConfiguration(final Configuration configuration) { - this.configuration = configuration; + public Builder setClob(final boolean clob) { + isClob = clob; return this; } /** - * The name of the database column as it exists within the database table. - * - * @return this. + * The configuration object. + * + * @return this. */ - public Builder setName(final String name) { - this.name = name; + public Builder setConfiguration(final Configuration configuration) { + this.configuration = configuration; return this; } /** - * The {@link PatternLayout} pattern to insert in this column. Mutually exclusive with - * {@code literal!=null} and {@code eventTimestamp=true} - * - * @return this. + * If {@code "true"}, indicates that this column is a date-time column in which the event timestamp should be + * inserted. Mutually exclusive with {@code pattern!=null} and {@code literal!=null}. + * + * @return this. */ - public Builder setPattern(final String pattern) { - this.pattern = pattern; + public Builder setEventTimestamp(final boolean eventTimestamp) { + isEventTimestamp = eventTimestamp; return this; } /** * The literal value to insert into the column as-is without any quoting or escaping. Mutually exclusive with * {@code pattern!=null} and {@code eventTimestamp=true}. - * - * @return this. + * + * @return this. */ public Builder setLiteral(final String literal) { this.literal = literal; @@ -188,71 +142,136 @@ public Builder setLiteral(final String literal) { } /** - * If {@code "true"}, indicates that this column is a date-time column in which the event timestamp should be - * inserted. Mutually exclusive with {@code pattern!=null} and {@code literal!=null}. - * - * @return this. + * The name of the database column as it exists within the database table. + * + * @return this. */ - public Builder setEventTimestamp(final boolean eventTimestamp) { - isEventTimestamp = eventTimestamp; + public Builder setName(final String name) { + this.name = name; return this; } /** - * If {@code "true"}, indicates that the column is a Unicode String. - * - * @return this. + * The {@link PatternLayout} pattern to insert in this column. Mutually exclusive with + * {@code literal!=null} and {@code eventTimestamp=true} + * + * @return this. */ - public Builder setUnicode(final boolean unicode) { - isUnicode = unicode; + public Builder setPattern(final String pattern) { + this.pattern = pattern; return this; } /** - * If {@code "true"}, indicates that the column is a character LOB (CLOB). - * - * @return this. + * If {@code "true"}, indicates that the column is a Unicode String. + * + * @return this. */ - public Builder setClob(final boolean clob) { - isClob = clob; + public Builder setUnicode(final boolean unicode) { + isUnicode = unicode; return this; } + } - @Override - public ColumnConfig build() { - if (Strings.isEmpty(name)) { - LOGGER.error("The column config is not valid because it does not contain a column name."); - return null; - } + private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * Factory method for creating a column config within the plugin manager. + * + * @see Builder + * @deprecated use {@link #newBuilder()} + */ + @Deprecated + public static ColumnConfig createColumnConfig( + final Configuration config, + final String name, + final String pattern, + final String literalValue, + final String eventTimestamp, + final String unicode, + final String clob) { + if (Strings.isEmpty(name)) { + LOGGER.error("The column config is not valid because it does not contain a column name."); + return null; + } - final boolean isPattern = Strings.isNotEmpty(pattern); - final boolean isLiteralValue = Strings.isNotEmpty(literal); + final boolean isEventTimestamp = Boolean.parseBoolean(eventTimestamp); + final boolean isUnicode = Booleans.parseBoolean(unicode, true); + final boolean isClob = Boolean.parseBoolean(clob); - if ((isPattern && isLiteralValue) || (isPattern && isEventTimestamp) || (isLiteralValue && isEventTimestamp)) { - LOGGER.error("The pattern, literal, and isEventTimestamp attributes are mutually exclusive."); - return null; - } + return newBuilder() + .setConfiguration(config) + .setName(name) + .setPattern(pattern) + .setLiteral(literalValue) + .setEventTimestamp(isEventTimestamp) + .setUnicode(isUnicode) + .setClob(isClob) + .build(); + } - if (isEventTimestamp) { - return new ColumnConfig(name, null, null, true, false, false); - } + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } - if (isLiteralValue) { - return new ColumnConfig(name, null, literal, false, false, false); - } + private final String columnName; + private final String columnNameKey; + private final PatternLayout layout; + private final String literalValue; - if (isPattern) { - final PatternLayout layout = - PatternLayout.newBuilder() - .withPattern(pattern) - .withConfiguration(configuration) - .withAlwaysWriteExceptions(false) - .build(); - return new ColumnConfig(name, layout, null, false, isUnicode, isClob); - } + private final boolean eventTimestamp; - LOGGER.error("To configure a column you must specify a pattern or literal or set isEventDate to true."); - return null; - } + private final boolean unicode; + + private final boolean clob; + + private ColumnConfig( + final String columnName, + final PatternLayout layout, + final String literalValue, + final boolean eventDate, + final boolean unicode, + final boolean clob) { + this.columnName = columnName; + this.columnNameKey = ColumnMapping.toKey(columnName); + this.layout = layout; + this.literalValue = literalValue; + this.eventTimestamp = eventDate; + this.unicode = unicode; + this.clob = clob; + } + + public String getColumnName() { + return this.columnName; + } + + public String getColumnNameKey() { + return this.columnNameKey; + } + + public PatternLayout getLayout() { + return this.layout; + } + + public String getLiteralValue() { + return this.literalValue; + } + + public boolean isClob() { + return this.clob; + } + + public boolean isEventTimestamp() { + return this.eventTimestamp; + } + + public boolean isUnicode() { + return this.unicode; + } + + @Override + public String toString() { + return "{ name=" + this.columnName + ", layout=" + this.layout + ", literal=" + this.literalValue + + ", timestamp=" + this.eventTimestamp + " }"; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java index 2a25b251826..7f97deb8bc9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/ConnectionSource.java @@ -1,32 +1,31 @@ /* * 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.logging.log4j.core.appender.db.jdbc; import java.sql.Connection; import java.sql.SQLException; - import org.apache.logging.log4j.core.LifeCycle; /** * Configuration element for {@link JdbcAppender}. If you want to use the {@link JdbcAppender} but none of the provided * connection sources meet your needs, you can simply create your own connection source. */ -public interface ConnectionSource extends LifeCycle{ - +public interface ConnectionSource extends LifeCycle { + /** * This should return a new connection every time it is called. * 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 index a791df98978..22a8bc7c7ab 100644 --- 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 @@ -1,33 +1,32 @@ /* * 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.logging.log4j.core.appender.db.jdbc; import java.sql.Connection; import java.sql.SQLException; - -import javax.naming.InitialContext; +import java.util.Objects; 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.core.net.JndiManager; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; @@ -42,7 +41,7 @@ public final class DataSourceConnectionSource extends AbstractConnectionSource { private final String description; private DataSourceConnectionSource(final String dataSourceName, final DataSource dataSource) { - this.dataSource = dataSource; + this.dataSource = Objects.requireNonNull(dataSource, "dataSource"); this.description = "dataSource{ name=" + dataSourceName + ", value=" + dataSource + " }"; } @@ -59,25 +58,29 @@ public String toString() { /** * 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. + * @param jndiName The full JNDI path where the data source is bound. Must start with java:/comp/env or environment-equivalent. * @return the created connection source. */ @PluginFactory - public static DataSourceConnectionSource createConnectionSource(@PluginAttribute("jndiName") final String jndiName) { + public static DataSourceConnectionSource createConnectionSource( + @PluginAttribute("jndiName") final String jndiName) { + if (!JndiManager.isJndiJdbcEnabled()) { + LOGGER.error("JNDI must be enabled by setting log4j2.enableJndiJdbc=true"); + return null; + } 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); + @SuppressWarnings("resource") + final DataSource dataSource = JndiManager.getDefaultManager( + DataSourceConnectionSource.class.getCanonicalName()) + .lookup(jndiName); if (dataSource == null) { - LOGGER.error("No data source found with JNDI name [" + jndiName + "]."); + LOGGER.error("No DataSource found with JNDI name [" + jndiName + "]."); return null; } - return new DataSourceConnectionSource(jndiName, dataSource); } catch (final NamingException e) { LOGGER.error(e.getMessage(), e); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java index e390c5e85e7..78b47f5bc88 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DriverManagerConnectionSource.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender.db.jdbc; import java.sql.DriverManager; - import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.Plugin; @@ -45,10 +44,14 @@ public static class Builder> extends AbstractDriverManagerC @Override public DriverManagerConnectionSource build() { - return new DriverManagerConnectionSource(getDriverClassName(), getConnectionString(), getConnectionString(), - getUserName(), getPassword(), getProperties()); + return new DriverManagerConnectionSource( + getDriverClassName(), + getConnectionString(), + getConnectionString(), + getUserName(), + getPassword(), + getProperties()); } - } @PluginBuilderFactory @@ -56,9 +59,13 @@ public static > B newBuilder() { return new Builder().asBuilder(); } - public DriverManagerConnectionSource(final String driverClassName, final String connectionString, - String actualConnectionString, final char[] userName, final char[] password, final Property[] properties) { + public DriverManagerConnectionSource( + final String driverClassName, + final String connectionString, + final String actualConnectionString, + final char[] userName, + final char[] password, + final Property[] properties) { super(driverClassName, connectionString, actualConnectionString, userName, password, properties); } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java index 20bf2f39540..ddaba532693 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/FactoryMethodConnectionSource.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db.jdbc; @@ -20,16 +20,14 @@ import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; - 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.core.util.Loader; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.Strings; /** @@ -43,8 +41,8 @@ public final class FactoryMethodConnectionSource extends AbstractConnectionSourc private final DataSource dataSource; private final String description; - private FactoryMethodConnectionSource(final DataSource dataSource, final String className, final String methodName, - final String returnType) { + private FactoryMethodConnectionSource( + final DataSource dataSource, final String className, final String methodName, final String returnType) { this.dataSource = dataSource; this.description = "factory{ public static " + returnType + ' ' + className + '.' + methodName + "() }"; } @@ -71,8 +69,7 @@ public String toString() { */ @PluginFactory public static FactoryMethodConnectionSource createConnectionSource( - @PluginAttribute("class") final String className, - @PluginAttribute("method") final String methodName) { + @PluginAttribute("class") final String className, @PluginAttribute("method") final String methodName) { if (Strings.isEmpty(className) || Strings.isEmpty(methodName)) { LOGGER.error("No class name or method name specified for the connection factory method."); return null; @@ -80,7 +77,7 @@ public static FactoryMethodConnectionSource createConnectionSource( final Method method; try { - final Class factoryClass = LoaderUtil.loadClass(className); + final Class factoryClass = Loader.loadClass(className); method = factoryClass.getMethod(methodName); } catch (final Exception e) { LOGGER.error(e.toString(), e); @@ -151,8 +148,8 @@ public T unwrap(final Class iface) throws SQLException { } }; } else { - LOGGER.error("Method [{}.{}()] returns unsupported type [{}].", className, methodName, - returnType.getName()); + LOGGER.error( + "Method [{}.{}()] returns unsupported type [{}].", className, methodName, returnType.getName()); 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 index 974d87ed16c..523994cea55 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.db.jdbc; @@ -20,7 +20,6 @@ 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; @@ -28,6 +27,7 @@ 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.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; @@ -52,62 +52,17 @@ @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 { + public static class Builder> extends AbstractDatabaseAppender.Builder + implements org.apache.logging.log4j.core.util.Builder { @PluginElement("ConnectionSource") @Required(message = "No ConnectionSource provided") private ConnectionSource connectionSource; + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private boolean immediateFail; + @PluginBuilderAttribute private int bufferSize; @@ -121,20 +76,55 @@ public static class Builder> extends AbstractAppender.Build @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(); + @PluginBuilderAttribute + private boolean truncateStrings = true; + + // TODO Consider moving up to AbstractDatabaseAppender.Builder. + @PluginBuilderAttribute + @SuppressWarnings("log4j.public.setter") + private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; + + @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, + immediateFail, + reconnectIntervalMillis, + truncateStrings); + if (manager == null) { + return null; + } + return new JdbcAppender( + getName(), getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), manager); + } + + public long getReconnectIntervalMillis() { + return reconnectIntervalMillis; + } + + public boolean isImmediateFail() { + return immediateFail; } /** * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer * reaches this size. - * + * + * @param bufferSize buffer size. + * * @return this */ public B setBufferSize(final int bufferSize) { @@ -143,46 +133,114 @@ public B setBufferSize(final int bufferSize) { } /** - * The name of the database table to insert log events into. - * + * Information about the columns that log event data should be inserted into and how to insert that data. + * + * @param columnConfigs Column configurations. + * * @return this */ - public B setTableName(final String tableName) { - this.tableName = tableName; + public B setColumnConfigs(final ColumnConfig... columnConfigs) { + this.columnConfigs = columnConfigs; + return asBuilder(); + } + + public B setColumnMappings(final ColumnMapping... columnMappings) { + this.columnMappings = columnMappings; return asBuilder(); } /** - * Information about the columns that log event data should be inserted into and how to insert that data. - * + * The connections source from which database connections should be retrieved. + * + * @param connectionSource The connections source. + * * @return this */ - public B setColumnConfigs(final ColumnConfig... columnConfigs) { - this.columnConfigs = columnConfigs; + public B setConnectionSource(final ConnectionSource connectionSource) { + this.connectionSource = connectionSource; return asBuilder(); } - public B setColumnMappings(final ColumnMapping... columnMappings) { - this.columnMappings = columnMappings; + public void setImmediateFail(final boolean immediateFail) { + this.immediateFail = immediateFail; + } + + public void setReconnectIntervalMillis(final long reconnectIntervalMillis) { + this.reconnectIntervalMillis = reconnectIntervalMillis; + } + + /** + * The name of the database table to insert log events into. + * + * @param tableName The database table name. + * + * @return this + */ + public B setTableName(final String tableName) { + this.tableName = tableName; 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); + public B setTruncateStrings(final boolean truncateStrings) { + this.truncateStrings = truncateStrings; + return asBuilder(); } + } + + /** + * 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) + .setName(name) + .setIgnoreExceptions(ignoreExceptions) + .setFilter(filter) + .build(); + } + + @PluginBuilderFactory + public static > B newBuilder() { + return new Builder().asBuilder(); + } + private final String description; + + private JdbcAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean ignoreExceptions, + final Property[] properties, + final JdbcDatabaseManager manager) { + super(name, filter, layout, ignoreExceptions, properties, manager); + this.description = this.getName() + "{ manager=" + this.getManager() + " }"; + } + + @Override + public String toString() { + return this.description; } } 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 index 2952288d347..54ac2aace98 100644 --- 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 @@ -1,21 +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. */ package org.apache.logging.log4j.core.appender.db.jdbc; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.Reader; import java.io.Serializable; import java.io.StringReader; import java.sql.Clob; @@ -23,28 +25,36 @@ import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; +import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.SQLTransactionRollbackException; +import java.sql.Statement; import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; - +import java.util.concurrent.CountDownLatch; 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.AbstractDatabaseAppender; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; import org.apache.logging.log4j.core.appender.db.ColumnMapping; +import org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException; 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.core.util.Log4jThread; 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; @@ -54,197 +64,364 @@ */ public final class JdbcDatabaseManager extends AbstractDatabaseManager { - private static StatusLogger logger() { - return StatusLogger.getLogger(); + /** + * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. + */ + static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final ConnectionSource connectionSource; + private final String tableName; + private final ColumnConfig[] columnConfigs; + final ColumnMapping[] columnMappings; + private final boolean immediateFail; + private final boolean retry; + private final long reconnectIntervalMillis; + private final boolean truncateStrings; + + protected FactoryData( + final int bufferSize, + final Layout layout, + final ConnectionSource connectionSource, + final String tableName, + final ColumnConfig[] columnConfigs, + final ColumnMapping[] columnMappings, + final boolean immediateFail, + final long reconnectIntervalMillis, + final boolean truncateStrings) { + super(bufferSize, layout); + this.connectionSource = connectionSource; + this.tableName = tableName; + this.columnConfigs = columnConfigs; + this.columnMappings = columnMappings; + this.immediateFail = immediateFail; + this.retry = reconnectIntervalMillis > 0; + this.reconnectIntervalMillis = reconnectIntervalMillis; + this.truncateStrings = truncateStrings; + } + + @Override + public String toString() { + return String.format( + "FactoryData [connectionSource=%s, tableName=%s, columnConfigs=%s, columnMappings=%s, immediateFail=%s, retry=%s, reconnectIntervalMillis=%s, truncateStrings=%s]", + connectionSource, + tableName, + Arrays.toString(columnConfigs), + Arrays.toString(columnMappings), + immediateFail, + retry, + reconnectIntervalMillis, + truncateStrings); + } } - private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); + /** + * Creates managers. + */ + private static final class JdbcDatabaseManagerFactory implements ManagerFactory { - // 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 static final char PARAMETER_MARKER = '?'; - private Connection connection; - private PreparedStatement statement; - private boolean isBatchSupported; + @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 + appendColumnNames("INSERT", data, sb); + sb.append(") values ("); + int i = 1; + if (data.columnMappings != null) { + 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()); + } else if (Strings.isNotEmpty(mapping.getParameter())) { + logger().trace( + "Adding INSERT VALUES parameter for ColumnMapping[{}]: {}={} ", + i, + mappingName, + mapping.getParameter()); + sb.append(mapping.getParameter()); + } else { + logger().trace( + "Adding INSERT VALUES parameter marker for ColumnMapping[{}]: {}={} ", + i, + mappingName, + PARAMETER_MARKER); + sb.append(PARAMETER_MARKER); + } + sb.append(','); + i++; + } + } + final int columnConfigsLen = data.columnConfigs == null ? 0 : data.columnConfigs.length; + final List columnConfigs = new ArrayList<>(columnConfigsLen); + if (data.columnConfigs != null) { + 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(); - 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; + return new JdbcDatabaseManager(name, sqlStatement, columnConfigs, data); + } } - @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); - } + /** + * Handles reconnecting to JDBC once on a Thread. + */ + private final class Reconnector extends Log4jThread { - @Override - protected boolean shutdownInternal() { - if (this.connection != null || this.statement != null) { - return this.commitAndClose(); + private final CountDownLatch latch = new CountDownLatch(1); + private volatile boolean shutdown; + + private Reconnector() { + super("JdbcDatabaseManager-Reconnector"); } - if (connectionSource != null) { - connectionSource.stop(); + + public void latch() { + try { + latch.await(); + } catch (final InterruptedException ex) { + // Ignore the exception. + } } - 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); + void reconnect() throws SQLException { + closeResources(false); + connectAndPrepare(); + reconnector = null; + shutdown = true; + logger().debug("Connection reestablished to {}", factoryData); } - } - @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()); + @Override + public void run() { + while (!shutdown) { + try { + sleep(factoryData.reconnectIntervalMillis); + reconnect(); + } catch (final InterruptedException | SQLException e) { + logger().debug( + "Cannot reestablish JDBC connection to {}: {}", + factoryData, + e.getLocalizedMessage(), + e); + } finally { + latch.countDown(); + } } - statement.setObject(i++, value); + } + + public void shutdown() { + shutdown = true; + } + + @Override + public String toString() { + return String.format("Reconnector [latch=%s, shutdown=%s]", latch, shutdown); } } - @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."); - } + private static final class ResultSetColumnMetaData { - 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)); - } - } + private final String schemaName; + private final String catalogName; + private final String tableName; + private final String name; + private final String nameKey; + private final String label; + private final int displaySize; + private final int type; + private final String typeName; + private final String className; + private final int precision; + private final int scale; + private final boolean isStringType; - 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); + public ResultSetColumnMetaData(final ResultSetMetaData rsMetaData, final int j) throws SQLException { + // @formatter:off + this( + rsMetaData.getSchemaName(j), + rsMetaData.getCatalogName(j), + rsMetaData.getTableName(j), + rsMetaData.getColumnName(j), + rsMetaData.getColumnLabel(j), + rsMetaData.getColumnDisplaySize(j), + rsMetaData.getColumnType(j), + rsMetaData.getColumnTypeName(j), + rsMetaData.getColumnClassName(j), + rsMetaData.getPrecision(j), + rsMetaData.getScale(j)); + // @formatter:on + } + + private ResultSetColumnMetaData( + final String schemaName, + final String catalogName, + final String tableName, + final String name, + final String label, + final int displaySize, + final int type, + final String typeName, + final String className, + final int precision, + final int scale) { + this.schemaName = schemaName; + this.catalogName = catalogName; + this.tableName = tableName; + this.name = name; + this.nameKey = ColumnMapping.toKey(name); + this.label = label; + this.displaySize = displaySize; + this.type = type; + this.typeName = typeName; + this.className = className; + this.precision = precision; + this.scale = scale; + // TODO How about also using the className? + // @formatter:off + this.isStringType = type == Types.CHAR + || type == Types.LONGNVARCHAR + || type == Types.LONGVARCHAR + || type == Types.NVARCHAR + || type == Types.VARCHAR; + // @formatter:on + } + + public String getCatalogName() { + return catalogName; + } + + public String getClassName() { + return className; + } + + public int getDisplaySize() { + return displaySize; + } + + public String getLabel() { + return label; + } + + public String getName() { + return name; + } + + public String getNameKey() { + return nameKey; + } + + public int getPrecision() { + return precision; + } + + public int getScale() { + return scale; + } + + public String getSchemaName() { + return schemaName; + } + + public String getTableName() { + return tableName; + } + + public int getType() { + return type; + } + + public String getTypeName() { + return typeName; + } + + public boolean isStringType() { + return this.isStringType; + } + + @Override + public String toString() { + return String.format( + "ColumnMetaData [schemaName=%s, catalogName=%s, tableName=%s, name=%s, nameKey=%s, label=%s, displaySize=%s, type=%s, typeName=%s, className=%s, precision=%s, scale=%s, isStringType=%s]", + schemaName, + catalogName, + tableName, + name, + nameKey, + label, + displaySize, + type, + typeName, + className, + precision, + scale, + isStringType); + } + + public String truncate(final String string) { + return precision > 0 ? Strings.left(string, precision) : string; } } - @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; - } + private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); - 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; + private static void appendColumnName(final int i, final String columnName, final StringBuilder sb) { + if (i > 1) { + sb.append(','); + } + sb.append(columnName); + } + + /** + * Appends column names to the given buffer in the format {@code "A,B,C"}. + */ + private static void appendColumnNames(final String sqlVerb, final FactoryData data, final StringBuilder sb) { + // 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; + final String messagePattern = "Appending {} {}[{}]: {}={} "; + if (data.columnMappings != null) { + for (final ColumnMapping colMapping : data.columnMappings) { + final String columnName = colMapping.getName(); + appendColumnName(i, columnName, sb); + logger().trace( + messagePattern, + sqlVerb, + colMapping.getClass().getSimpleName(), + i, + columnName, + colMapping); + i++; } } - return closed; + if (data.columnConfigs != null) { + for (final ColumnConfig colConfig : data.columnConfigs) { + final String columnName = colConfig.getColumnName(); + appendColumnName(i, columnName, sb); + logger().trace(messagePattern, sqlVerb, colConfig.getClass().getSimpleName(), i, columnName, colConfig); + i++; + } + } + } + + private static JdbcDatabaseManagerFactory getFactory() { + return INSTANCE; } /** @@ -256,17 +433,65 @@ protected boolean commitAndClose() { * @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 use + * {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[], boolean, long)} */ @Deprecated - public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs) { + 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, + ColumnMapping.EMPTY_ARRAY, + false, + AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, + true), + getFactory()); + } - 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 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. + */ + @Deprecated + 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, + false, + AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, + true), + getFactory()); } /** @@ -274,22 +499,42 @@ public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, fina * * @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). + * @param reconnectIntervalMillis How often to reconnect to the database when a SQL exception is detected. + * @param immediateFail Whether to fail immediately with a {@link AppenderLoggingException} when connecting + * to JDBC fails. * @return a new or existing JDBC manager as applicable. - * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])} + * @deprecated use + * {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[], boolean, long)} */ @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()); + 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, + final boolean immediateFail, + final long reconnectIntervalMillis) { + return getManager( + name, + new FactoryData( + bufferSize, + null, + connectionSource, + tableName, + columnConfigs, + columnMappings, + false, + AbstractDatabaseAppender.DEFAULT_RECONNECT_INTERVAL_MILLIS, + true), + getFactory()); } /** @@ -302,104 +547,522 @@ public static JdbcDatabaseManager getManager(final String name, * @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). + * @param immediateFail Whether or not to fail immediately with a {@link AppenderLoggingException} when connecting + * to JDBC fails. + * @param reconnectIntervalMillis How often to reconnect to the database when a SQL exception is detected. + * @param truncateStrings Whether or not to truncate strings to match column metadata. * @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()); + 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, + final boolean immediateFail, + final long reconnectIntervalMillis, + final boolean truncateStrings) { + return getManager( + name, + new FactoryData( + bufferSize, + layout, + connectionSource, + tableName, + columnConfigs, + columnMappings, + immediateFail, + reconnectIntervalMillis, + truncateStrings), + getFactory()); } - private static JdbcDatabaseManagerFactory getFactory() { - return INSTANCE; + // NOTE: prepared statements are prepared in this order: column mappings, then column configs + private final List columnConfigs; + private final String sqlStatement; + // Used in tests + final FactoryData factoryData; + private volatile Connection connection; + private volatile PreparedStatement statement; + private volatile Reconnector reconnector; + private volatile boolean isBatchSupported; + private volatile Map columnMetaData; + + private JdbcDatabaseManager( + final String name, + final String sqlStatement, + final List columnConfigs, + final FactoryData factoryData) { + super(name, factoryData.getBufferSize()); + this.sqlStatement = sqlStatement; + this.columnConfigs = columnConfigs; + this.factoryData = factoryData; + } + + private void checkConnection() { + boolean connClosed = true; + try { + connClosed = isClosed(this.connection); + } catch (final SQLException e) { + // Be quiet + } + boolean stmtClosed = true; + try { + stmtClosed = isClosed(this.statement); + } catch (final SQLException e) { + // Be quiet + } + if (!this.isRunning() || connClosed || stmtClosed) { + // If anything is closed, close it all down before we reconnect + closeResources(false); + // Reconnect + if (reconnector != null && !factoryData.immediateFail) { + reconnector.latch(); + if (connection == null) { + throw new AppenderLoggingException( + "Error writing to JDBC Manager '%s': JDBC connection not available [%s]", + getName(), fieldsToString()); + } + if (statement == null) { + throw new AppenderLoggingException( + "Error writing to JDBC Manager '%s': JDBC statement not available [%s].", + getName(), connection, fieldsToString()); + } + } + } + } + + protected void closeResources(final boolean logExceptions) { + final PreparedStatement tempPreparedStatement = this.statement; + this.statement = null; + try { + // Closing a statement returns it to the pool when using Apache Commons DBCP. + // Closing an already closed statement has no effect. + Closer.close(tempPreparedStatement); + } catch (final Exception e) { + if (logExceptions) { + logWarn("Failed to close SQL statement logging event or flushing buffer", e); + } + } + + final Connection tempConnection = this.connection; + this.connection = null; + try { + // Closing a connection returns it to the pool when using Apache Commons DBCP. + // Closing an already closed connection has no effect. + Closer.close(tempConnection); + } catch (final Exception e) { + if (logExceptions) { + logWarn("Failed to close database connection logging event or flushing buffer", e); + } + } + } + + @Override + protected boolean commitAndClose() { + final boolean closed = true; + try { + if (this.connection != null && !this.connection.isClosed()) { + if (isBuffered() && this.isBatchSupported && this.statement != null) { + logger().debug("Executing batch PreparedStatement {}", this.statement); + int[] result; + try { + result = this.statement.executeBatch(); + } catch (SQLTransactionRollbackException e) { + logger().debug("{} executing batch PreparedStatement {}, retrying.", e, this.statement); + result = this.statement.executeBatch(); + } + logger().debug("Batch result: {}", Arrays.toString(result)); + } + logger().debug("Committing Connection {}", this.connection); + this.connection.commit(); + } + } catch (final SQLException e) { + throw new DbAppenderLoggingException( + e, "Failed to commit transaction logging event or flushing buffer [%s]", fieldsToString()); + } finally { + closeResources(true); + } + return closed; + } + + private boolean commitAndCloseAll() { + if (this.connection != null || this.statement != null) { + try { + this.commitAndClose(); + return true; + } catch (final AppenderLoggingException e) { + // Database connection has likely gone stale. + final Throwable cause = e.getCause(); + final Throwable actual = cause == null ? e : cause; + logger().debug( + "{} committing and closing connection: {}", + actual, + actual.getClass().getSimpleName(), + e.toString(), + e); + } + } + if (factoryData.connectionSource != null) { + factoryData.connectionSource.stop(); + } + return true; + } + + @SuppressFBWarnings( + value = "SQL_INJECTION_JDBC", + justification = "The SQL statement is generated based on the configuration file.") + private void connectAndPrepare() throws SQLException { + logger().debug("Acquiring JDBC connection from {}", this.getConnectionSource()); + this.connection = getConnectionSource().getConnection(); + logger().debug("Acquired JDBC connection {}", this.connection); + logger().debug("Getting connection metadata {}", this.connection); + final DatabaseMetaData databaseMetaData = this.connection.getMetaData(); + logger().debug("Connection metadata {}", databaseMetaData); + this.isBatchSupported = databaseMetaData.supportsBatchUpdates(); + logger().debug("Connection supportsBatchUpdates: {}", this.isBatchSupported); + this.connection.setAutoCommit(false); + logger().debug("Preparing SQL {}", this.sqlStatement); + this.statement = this.connection.prepareStatement(this.sqlStatement); + logger().debug("Prepared SQL {}", this.statement); + if (this.factoryData.truncateStrings) { + initColumnMetaData(); + } + } + + @Override + protected void connectAndStart() { + checkConnection(); + synchronized (this) { + try { + connectAndPrepare(); + } catch (final SQLException e) { + reconnectOn(e); + } + } + } + + private Reconnector createReconnector() { + final Reconnector recon = new Reconnector(); + recon.setDaemon(true); + recon.setPriority(Thread.MIN_PRIORITY); + return recon; + } + + private String createSqlSelect() { + final StringBuilder sb = new StringBuilder("select "); + appendColumnNames("SELECT", this.factoryData, sb); + sb.append(" from "); + sb.append(this.factoryData.tableName); + sb.append(" where 1=0"); + return sb.toString(); + } + + private String fieldsToString() { + return String.format( + "columnConfigs=%s, sqlStatement=%s, factoryData=%s, connection=%s, statement=%s, reconnector=%s, isBatchSupported=%s, columnMetaData=%s", + columnConfigs, + sqlStatement, + factoryData, + connection, + statement, + reconnector, + isBatchSupported, + columnMetaData); + } + + public ConnectionSource getConnectionSource() { + return factoryData.connectionSource; + } + + public String getSqlStatement() { + return sqlStatement; + } + + public String getTableName() { + return factoryData.tableName; + } + + @SuppressFBWarnings( + value = "SQL_INJECTION_JDBC", + justification = "The SQL statement is generated based on the configuration file.") + private void initColumnMetaData() throws SQLException { + // Could use: + // this.connection.getMetaData().getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern); + // But this returns more data than we need for now, so do a SQL SELECT with 0 result rows instead. + final String sqlSelect = createSqlSelect(); + logger().debug("Getting SQL metadata for table {}: {}", this.factoryData.tableName, sqlSelect); + try (final PreparedStatement mdStatement = this.connection.prepareStatement(sqlSelect)) { + final ResultSetMetaData rsMetaData = mdStatement.getMetaData(); + logger().debug("SQL metadata: {}", rsMetaData); + if (rsMetaData != null) { + final int columnCount = rsMetaData.getColumnCount(); + columnMetaData = new HashMap<>(columnCount); + for (int i = 0, j = 1; i < columnCount; i++, j++) { + final ResultSetColumnMetaData value = new ResultSetColumnMetaData(rsMetaData, j); + columnMetaData.put(value.getNameKey(), value); + } + } else { + logger().warn( + "{}: truncateStrings is true and ResultSetMetaData is null for statement: {}; manager will not perform truncation.", + getClass().getSimpleName(), + mdStatement); + } + } } /** - * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. + * Checks if a statement is closed. A null statement is considered closed. + * + * @param statement The statement to check. + * @return true if a statement is closed, false if null. + * @throws SQLException if a database access error occurs */ - private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final ConnectionSource connectionSource; - private final String tableName; - private final ColumnConfig[] columnConfigs; - private final ColumnMapping[] columnMappings; + private boolean isClosed(final Statement statement) throws SQLException { + return statement == null || statement.isClosed(); + } - 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; + /** + * Checks if a connection is closed. A null connection is considered closed. + * + * @param connection The connection to check. + * @return true if a connection is closed, false if null. + * @throws SQLException if a database access error occurs + */ + private boolean isClosed(final Connection connection) throws SQLException { + return connection == null || connection.isClosed(); + } + + private void reconnectOn(final Exception exception) { + if (!factoryData.retry) { + throw new AppenderLoggingException("Cannot connect and prepare", exception); + } + if (reconnector == null) { + reconnector = createReconnector(); + try { + reconnector.reconnect(); + } catch (final SQLException reconnectEx) { + logger().debug( + "Cannot reestablish JDBC connection to {}: {}; starting reconnector thread {}", + factoryData, + reconnectEx, + reconnector.getName(), + reconnectEx); + reconnector.start(); + reconnector.latch(); + if (connection == null || statement == null) { + throw new AppenderLoggingException( + exception, "Error sending to %s for %s [%s]", getName(), factoryData, fieldsToString()); + } + } + } + } + + private void setFields(final MapMessage mapMessage) throws SQLException { + final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap(); + final String simpleName = statement.getClass().getName(); + int j = 1; // JDBC indices start at 1 + if (this.factoryData.columnMappings != null) { + for (final ColumnMapping mapping : this.factoryData.columnMappings) { + if (mapping.getLiteralValue() == null) { + 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, + j, + valueStr, + key, + mapping.getName()); + } + setStatementObject(j, mapping.getNameKey(), value); + j++; + } + } } } /** - * Creates managers. + * Sets the given Object in the prepared statement. The value is truncated if needed. */ - private static final class JdbcDatabaseManagerFactory implements ManagerFactory { - - private static final char PARAMETER_MARKER = '?'; + private void setStatementObject(final int j, final String nameKey, final Object value) throws SQLException { + if (statement == null) { + throw new AppenderLoggingException("Cannot set a value when the PreparedStatement is null."); + } + if (value == null) { + if (columnMetaData == null) { + throw new AppenderLoggingException("Cannot set a value when the column metadata is null."); + } + // [LOG4J2-2762] [JDBC] MS-SQL Server JDBC driver throws SQLServerException when + // inserting a null value for a VARBINARY column. + // Calling setNull() instead of setObject() for null values fixes [LOG4J2-2762]. + this.statement.setNull(j, columnMetaData.get(nameKey).getType()); + } else { + statement.setObject(j, truncate(nameKey, value)); + } + } - @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(','); + @Override + protected boolean shutdownInternal() { + if (reconnector != null) { + reconnector.shutdown(); + reconnector.interrupt(); + reconnector = null; + } + return commitAndCloseAll(); + } + + @Override + protected void startupInternal() throws Exception { + // empty + } + + /** + * Truncates the value if needed. + */ + private Object truncate(final String nameKey, Object value) { + if (value != null && this.factoryData.truncateStrings && columnMetaData != null) { + final ResultSetColumnMetaData resultSetColumnMetaData = columnMetaData.get(nameKey); + if (resultSetColumnMetaData != null) { + if (resultSetColumnMetaData.isStringType()) { + value = resultSetColumnMetaData.truncate(value.toString()); + } + } else { + logger().error( + "Missing ResultSetColumnMetaData for {}, connection={}, statement={}", + nameKey, + connection, + statement); } - for (final ColumnConfig config : data.columnConfigs) { - sb.append(config.getColumnName()).append(','); + } + return value; + } + + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + // Don't close StringReaders because of (1) batching, (2) resources are not allocated, and (3) they'll be GC'd + // away. + // See https://github.com/apache/logging-log4j2/issues/3127 where closing StringReaders too soon can cause + // problems. + try { + if (!this.isRunning() || isClosed(this.connection) || isClosed(this.statement)) { + throw new AppenderLoggingException( + "Cannot write logging event; JDBC manager not connected to the database, running=%s, [%s]).", + isRunning(), fieldsToString()); } - // 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); + // Clear in case there are leftovers. + statement.clearParameters(); + if (serializable instanceof MapMessage) { + setFields((MapMessage) serializable); + } + int j = 1; // JDBC indices start at 1 + if (this.factoryData.columnMappings != null) { + for (final ColumnMapping mapping : this.factoryData.columnMappings) { + if (ThreadContextMap.class.isAssignableFrom(mapping.getType()) + || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject(j++, event.getContextData().toMap()); + } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject(j++, event.getContextStack().asList()); + } else if (Date.class.isAssignableFrom(mapping.getType())) { + this.statement.setObject( + j++, + DateTypeConverter.fromMillis( + event.getTimeMillis(), mapping.getType().asSubclass(Date.class))); + } else { + final StringLayout layout = mapping.getLayout(); + if (layout != null) { + if (Clob.class.isAssignableFrom(mapping.getType())) { + this.statement.setClob(j++, new StringReader(layout.toSerializable(event))); + } else if (NClob.class.isAssignableFrom(mapping.getType())) { + this.statement.setNClob(j++, new StringReader(layout.toSerializable(event))); + } else { + final Object value = + TypeConverters.convert(layout.toSerializable(event), mapping.getType(), null); + setStatementObject(j++, mapping.getNameKey(), value); + } + } + } } - 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()); + for (final ColumnConfig column : this.columnConfigs) { + if (column.isEventTimestamp()) { + this.statement.setTimestamp(j++, new Timestamp(event.getTimeMillis())); + } else if (column.isClob()) { + final Reader reader = new StringReader(column.getLayout().toSerializable(event)); + if (column.isUnicode()) { + this.statement.setNClob(j++, reader); + } else { + this.statement.setClob(j++, reader); + } + } else if (column.isUnicode()) { + this.statement.setNString( + j++, + Objects.toString( + truncate( + column.getColumnNameKey(), + column.getLayout().toSerializable(event)), + null)); } else { - sb.append(PARAMETER_MARKER); - columnConfigs.add(config); + this.statement.setString( + j++, + Objects.toString( + truncate( + column.getColumnNameKey(), + column.getLayout().toSerializable(event)), + null)); } - 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); + if (isBuffered() && this.isBatchSupported) { + logger().debug("addBatch for {}", this.statement); + this.statement.addBatch(); + } else { + final int executeUpdate = this.statement.executeUpdate(); + logger().debug("executeUpdate = {} for {}", executeUpdate, this.statement); + if (executeUpdate == 0) { + throw new AppenderLoggingException( + "No records inserted in database table for log event in JDBC manager [%s].", + fieldsToString()); + } + } + } catch (final SQLException e) { + throw new DbAppenderLoggingException( + e, "Failed to insert record for log event in JDBC manager: %s [%s]", e, fieldsToString()); + } finally { + // Release ASAP + try { + // statement can be null when a AppenderLoggingException is thrown at the start of this method + if (statement != null) { + statement.clearParameters(); + } + } catch (final SQLException e) { + // Ignore + } } } + @Override + protected void writeThrough(final LogEvent event, final Serializable serializable) { + this.connectAndStart(); + try { + try { + this.writeInternal(event, serializable); + } finally { + this.commitAndClose(); + } + } catch (final DbAppenderLoggingException e) { + reconnectOn(e); + try { + this.writeInternal(event, serializable); + } finally { + this.commitAndClose(); + } + } + } } 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 index 1dbd663c4e3..49a64405d80 100644 --- 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 @@ -18,4 +18,9 @@ * 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. */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.core.appender.db.jdbc; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java index 9f8312df6a3..eacc1321f7e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/package-info.java @@ -18,4 +18,9 @@ * The classes in this package and sub packages provide appenders for various types of databases and methods for * accessing databases. */ +@Export +@Version("2.21.0") package org.apache.logging.log4j.core.appender.db; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 index 17040186d82..b6df7dd08dd 100644 --- 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 @@ -1,28 +1,25 @@ /* * 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.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; @@ -31,30 +28,30 @@ 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.Property; 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. + * Javax JMS Appender plugin. This Appender replaces the previous split classes. + * Configurations set up for the 2.0 version of the JMS appenders will still work. + * + * @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsAppender}. */ -@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) -@PluginAliases({ "JMSQueue", "JMSTopic" }) +@Deprecated +@Plugin(name = "JMS-Javax", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) +@PluginAliases({"JMS", "JMSQueue", "JMSTopic"}) public class JmsAppender extends AbstractAppender { - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder> extends AbstractAppender.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; @@ -75,7 +72,7 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde private String factoryBindingName; @PluginBuilderAttribute - @PluginAliases({ "queueBindingName", "topicBindingName" }) + @PluginAliases({"queueBindingName", "topicBindingName"}) @Required(message = "A javax.jms.Destination JNDI name must be specified") private String destinationBindingName; @@ -85,16 +82,8 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @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; + private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; @PluginBuilderAttribute private boolean immediateFail; @@ -102,8 +91,7 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde // Programmatic access only for now. private JmsManager jmsManager; - private Builder() { - } + private Builder() {} @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender @Override @@ -111,24 +99,32 @@ 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); + final Properties jndiProperties = JndiManager.createProperties( + factoryName, providerUrl, urlPkgPrefixes, securityPrincipalName, securityCredentials, null); + configuration = new JmsManagerConfiguration( + jndiProperties, + factoryBindingName, + destinationBindingName, + userName, + password, + false, + reconnectIntervalMillis); + actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration); } if (actualJmsManager == null) { // JmsManagerFactory has already logged an ERROR. return null; } + final Layout layout = getLayout(); if (layout == null) { LOGGER.error("No layout provided for JmsAppender"); return null; } try { - return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager); + return new JmsAppender( + getName(), getFilter(), layout, isIgnoreExceptions(), getPropertyArray(), actualJmsManager); } catch (final JMSException e) { - // Never happens since the ctor no longer actually throws a JMSException. + // Never happens since the ctor no longer actually throws a JMSException. throw new IllegalStateException(e); } } @@ -148,16 +144,6 @@ public Builder setFactoryName(final String 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; @@ -168,16 +154,6 @@ public Builder setJmsManager(final 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; @@ -236,14 +212,13 @@ public Builder setUserName(final String userName) { */ @Override public String toString() { - return "Builder [name=" + name + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl + return "Builder [name=" + getName() + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout=" - + layout + ", filter=" + filter + ", ignoreExceptions=" + ignoreExceptions + ", jmsManager=" - + jmsManager + "]"; + + getLayout() + ", filter=" + getFilter() + ", ignoreExceptions=" + isIgnoreExceptions() + + ", jmsManager=" + jmsManager + "]"; } - } @PluginBuilderFactory @@ -254,13 +229,37 @@ public static Builder newBuilder() { private volatile JmsManager manager; /** + * Constructs a new instance. + * + * @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 Property[] properties, + final JmsManager manager) + throws JMSException { + super(name, filter, layout, ignoreExceptions, properties); + this.manager = manager; + } + + /** + * Constructs a new instance. * - * @throws JMSException - * not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0 + * @throws JMSException not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0 + * @deprecated Use {@link #JmsAppender(String, Filter, Layout, boolean, Property[], JmsManager)}. */ - protected JmsAppender(final String name, final Filter filter, final Layout layout, - final boolean ignoreExceptions, final JmsManager manager) throws JMSException { - super(name, filter, layout, ignoreExceptions); + @Deprecated + protected JmsAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean ignoreExceptions, + final JmsManager manager) + throws JMSException { + super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); this.manager = manager; } @@ -281,5 +280,4 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopped(); return stopped; } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java index f7794e664b7..178d1f428af 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsManager.java @@ -1,27 +1,25 @@ /* * 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.logging.log4j.core.appender.mom; import java.io.Serializable; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; @@ -32,8 +30,6 @@ import javax.jms.MessageProducer; import javax.jms.Session; import javax.naming.NamingException; - -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractManager; import org.apache.logging.log4j.core.appender.AppenderLoggingException; @@ -41,16 +37,17 @@ import org.apache.logging.log4j.core.net.JndiManager; import org.apache.logging.log4j.core.util.Log4jThread; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.BiConsumer; /** * Consider this class private; it is only public for access by integration tests. * *

- * JMS connection and session manager. Can be used to access MessageProducer, MessageConsumer, and Message objects - * involving a configured ConnectionFactory and Destination. + * JMS connection and destination manager. Uses a MessageProducer to send log events to a JMS Destination. *

+ * + * @deprecated Use {@code org.apache.logging.log4j.core.appender.mom.jakarta.JmsManager}. */ +@Deprecated public class JmsManager extends AbstractManager { public static class JmsManagerConfiguration { @@ -63,8 +60,13 @@ public static class JmsManagerConfiguration { private final boolean retry; private final long reconnectIntervalMillis; - JmsManagerConfiguration(final Properties jndiProperties, final String connectionFactoryName, - final String destinationName, final String userName, final char[] password, final boolean immediateFail, + JmsManagerConfiguration( + final Properties jndiProperties, + final String connectionFactoryName, + final String destinationName, + final String userName, + final char[] password, + final boolean immediateFail, final long reconnectIntervalMillis) { this.jndiProperties = jndiProperties; this.connectionFactoryName = connectionFactoryName; @@ -119,34 +121,37 @@ public String toString() { + ", immediateFail=" + immediateFail + ", retry=" + retry + ", reconnectIntervalMillis=" + reconnectIntervalMillis + "]"; } - } private static class JmsManagerFactory implements ManagerFactory { @Override public JmsManager createManager(final String name, final JmsManagerConfiguration data) { - try { - return new JmsManager(name, data); - } catch (final Exception e) { - LOGGER.error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e); - return null; + if (JndiManager.isJndiJmsEnabled()) { + try { + return new JmsManager(name, data); + } catch (final Exception e) { + logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e); + return null; + } } + logger().error("JNDI must be enabled by setting log4j2.enableJndiJms=true"); + return null; } } /** - * Handles reconnecting to a Socket on a Thread. + * Handles reconnecting to JMS on a Thread. */ - private class Reconnector extends Log4jThread { + private final class Reconnector extends Log4jThread { private final CountDownLatch latch = new CountDownLatch(1); - private volatile boolean shutdown = false; + private volatile boolean shutdown; private final Object owner; - public Reconnector(final Object owner) { + private Reconnector(final Object owner) { super("JmsManager-Reconnector"); this.owner = owner; } @@ -175,7 +180,7 @@ void reconnect() throws NamingException, JMSException { reconnector = null; shutdown = true; } - LOGGER.debug("Connection reestablished to {}", configuration); + logger().debug("Connection reestablished to {}", configuration); } @Override @@ -185,8 +190,11 @@ public void run() { sleep(configuration.getReconnectIntervalMillis()); reconnect(); } catch (final InterruptedException | JMSException | NamingException e) { - LOGGER.debug("Cannot reestablish JMS connection to {}: {}", configuration, e.getLocalizedMessage(), - e); + logger().debug( + "Cannot reestablish JMS connection to {}: {}", + configuration, + e.getLocalizedMessage(), + e); } finally { latch.countDown(); } @@ -196,11 +204,8 @@ public void run() { public void shutdown() { shutdown = true; } - } - private static final Logger LOGGER = StatusLogger.getLogger(); - static final JmsManagerFactory FACTORY = new JmsManagerFactory(); /** @@ -221,15 +226,27 @@ public void shutdown() { * fails. * @param reconnectIntervalMillis * How to log sleep in milliseconds before trying to reconnect to JMS. - * @param jndiManager - * The JndiManager to look up JMS information through. + * @param jndiProperties + * JNDI properties. * @return The JmsManager as configured. */ - public static JmsManager getJmsManager(final String name, final Properties jndiProperties, - final String connectionFactoryName, final String destinationName, final String userName, - final char[] password, final boolean immediateFail, final long reconnectIntervalMillis) { - final JmsManagerConfiguration configuration = new JmsManagerConfiguration(jndiProperties, connectionFactoryName, - destinationName, userName, password, immediateFail, reconnectIntervalMillis); + public static JmsManager getJmsManager( + final String name, + final Properties jndiProperties, + final String connectionFactoryName, + final String destinationName, + final String userName, + final char[] password, + final boolean immediateFail, + final long reconnectIntervalMillis) { + final JmsManagerConfiguration configuration = new JmsManagerConfiguration( + jndiProperties, + connectionFactoryName, + destinationName, + userName, + password, + immediateFail, + reconnectIntervalMillis); return getManager(name, FACTORY, configuration); } @@ -268,9 +285,12 @@ private boolean closeConnection() { temp.close(); return true; } catch (final JMSException e) { - StatusLogger.getLogger().debug( - "Caught exception closing JMS Connection: {} ({}); continuing JMS manager shutdown", - e.getLocalizedMessage(), temp, e); + StatusLogger.getLogger() + .debug( + "Caught exception closing JMS Connection: {} ({}); continuing JMS manager shutdown", + e.getLocalizedMessage(), + temp, + e); return false; } } @@ -295,9 +315,12 @@ private boolean closeMessageProducer() { temp.close(); return true; } catch (final JMSException e) { - StatusLogger.getLogger().debug( - "Caught exception closing JMS MessageProducer: {} ({}); continuing JMS manager shutdown", - e.getLocalizedMessage(), temp, e); + StatusLogger.getLogger() + .debug( + "Caught exception closing JMS MessageProducer: {} ({}); continuing JMS manager shutdown", + e.getLocalizedMessage(), + temp, + e); return false; } } @@ -312,9 +335,12 @@ private boolean closeSession() { temp.close(); return true; } catch (final JMSException e) { - StatusLogger.getLogger().debug( - "Caught exception closing JMS Session: {} ({}); continuing JMS manager shutdown", - e.getLocalizedMessage(), temp, e); + StatusLogger.getLogger() + .debug( + "Caught exception closing JMS Session: {} ({}); continuing JMS manager shutdown", + e.getLocalizedMessage(), + temp, + e); return false; } } @@ -322,11 +348,11 @@ private boolean closeSession() { private Connection createConnection(final JndiManager jndiManager) throws NamingException, JMSException { final ConnectionFactory connectionFactory = jndiManager.lookup(configuration.getConnectionFactoryName()); if (configuration.getUserName() != null && configuration.getPassword() != null) { - return connectionFactory.createConnection(configuration.getUserName(), + return connectionFactory.createConnection( + configuration.getUserName(), configuration.getPassword() == null ? null : String.valueOf(configuration.getPassword())); } return connectionFactory.createConnection(); - } private Destination createDestination(final JndiManager jndiManager) throws NamingException { @@ -352,7 +378,7 @@ private Destination createDestination(final JndiManager jndiManager) throws Nami * @param object * The LogEvent or String message to wrap. * @return A new JMS message containing the provided object. - * @throws JMSException + * @throws JMSException if the JMS provider fails to create this message due to some internal error. */ public Message createMessage(final Serializable object) throws JMSException { if (object instanceof String) { @@ -373,7 +399,7 @@ private void createMessageAndSend(final LogEvent event, final Serializable seria * Creates a MessageConsumer on this Destination using the current Session. * * @return A MessageConsumer on this Destination. - * @throws JMSException + * @throws JMSException if the session fails to create a consumer due to some internal error. */ public MessageConsumer createMessageConsumer() throws JMSException { return this.session.createConsumer(this.destination); @@ -387,7 +413,7 @@ public MessageConsumer createMessageConsumer() throws JMSException { * @param destination * The JMS Destination for the MessageProducer * @return A MessageProducer on this Destination. - * @throws JMSException + * @throws JMSException if the session fails to create a MessageProducer due to some internal error. */ public MessageProducer createMessageProducer(final Session session, final Destination destination) throws JMSException { @@ -417,18 +443,18 @@ T lookup(final String destinationName) throws NamingException { return this.jndiManager.lookup(destinationName); } - private MapMessage map(final org.apache.logging.log4j.message.MapMessage log4jMapMessage, - final MapMessage jmsMapMessage) { + private MapMessage map( + final org.apache.logging.log4j.message.MapMessage log4jMapMessage, final MapMessage jmsMapMessage) { // Map without calling org.apache.logging.log4j.message.MapMessage#getData() which makes a copy of the map. - log4jMapMessage.forEach(new BiConsumer() { - @Override - public void accept(final String key, final Object value) { - try { - jmsMapMessage.setObject(key, value); - } catch (final JMSException e) { - throw new IllegalArgumentException(String.format("%s mapping key '%s' to value '%s': %s", - e.getClass(), key, value, e.getLocalizedMessage()), e); - } + log4jMapMessage.forEach((key, value) -> { + try { + jmsMapMessage.setObject(key, value); + } catch (final JMSException e) { + throw new IllegalArgumentException( + String.format( + "%s mapping key '%s' to value '%s': %s", + e.getClass(), key, value, e.getLocalizedMessage()), + e); } }); return jmsMapMessage; @@ -453,10 +479,10 @@ void send(final LogEvent event, final Serializable serializable) { if (messageProducer == null) { if (reconnector != null && !configuration.isImmediateFail()) { reconnector.latch(); - } - if (messageProducer == null) { - throw new AppenderLoggingException( - "Error sending to JMS Manager '" + getName() + "': JMS message producer not available"); + if (messageProducer == null) { + throw new AppenderLoggingException( + "Error sending to JMS Manager '" + getName() + "': JMS message producer not available"); + } } } synchronized (this) { @@ -469,23 +495,27 @@ void send(final LogEvent event, final Serializable serializable) { closeJndiManager(); reconnector.reconnect(); } catch (NamingException | JMSException reconnEx) { - LOGGER.debug("Cannot reestablish JMS connection to {}: {}; starting reconnector thread {}", - configuration, reconnEx.getLocalizedMessage(), reconnector.getName(), reconnEx); + logger().debug( + "Cannot reestablish JMS connection to {}: {}; starting reconnector thread {}", + configuration, + reconnEx.getLocalizedMessage(), + reconnector.getName(), + reconnEx); reconnector.start(); throw new AppenderLoggingException( - String.format("Error sending to %s for %s", getName(), configuration), causeEx); + String.format("JMS exception sending to %s for %s", getName(), configuration), causeEx); } try { createMessageAndSend(event, serializable); } catch (final JMSException e) { throw new AppenderLoggingException( - String.format("Error sending to %s after reestablishing connection for %s", getName(), - configuration), + String.format( + "Error sending to %s after reestablishing JMS connection for %s", + getName(), configuration), causeEx); } } } } } - } 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 index aa78fad87d8..c2598c1332c 100644 --- 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 @@ -1,27 +1,25 @@ /* * 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.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; @@ -63,18 +61,57 @@ public final class JeroMqAppender extends AbstractAppender { 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); + 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, + final Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); + 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; } @@ -111,7 +148,7 @@ public static JeroMqAppender createAppender( @PluginAttribute(value = "tcpKeepAliveInterval", defaultLong = -1) final long tcpKeepAliveInterval, @PluginAttribute(value = "xpubVerbose") final boolean xpubVerbose // @formatter:on - ) { + ) { if (layout == null) { layout = PatternLayout.createDefaultLayout(); } @@ -129,12 +166,40 @@ public static JeroMqAppender createAppender( } } } - 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); + 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, + null); } @Override @@ -145,7 +210,8 @@ public synchronized void append(final LogEvent event) { sendRcTrue++; } else { sendRcFalse++; - LOGGER.error("Appender {} could not send message {} to JeroMQ {}", getName(), sendRcFalse, formattedMessage); + LOGGER.error( + "Appender {} could not send message {} to JeroMQ {}", getName(), sendRcFalse, formattedMessage); } } @@ -158,28 +224,40 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { return stopped; } - // not public, handy for testing - int getSendRcFalse() { + /** + * Used in tests + */ + synchronized int getSendRcFalse() { return sendRcFalse; } - // not public, handy for testing - int getSendRcTrue() { + /** + * Used in tests + */ + synchronized int getSendRcTrue() { return sendRcTrue; } - // not public, handy for testing - void resetSendRcs() { + /** + * Used in tests + */ + synchronized void resetSendRcs() { sendRcTrue = sendRcFalse = 0; } + /** + * Used in tests + */ + JeroMqManager getManager() { + return manager; + } + @Override public String toString() { - return "JeroMqAppender{" + - "name=" + getName() + - ", state=" + getState() + - ", manager=" + manager + - ", endpoints=" + endpoints + - '}'; + 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/JeroMqManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqManager.java index a438faf0752..8e415093228 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqManager.java @@ -1,222 +1,299 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.appender.AbstractManager; -import org.apache.logging.log4j.core.appender.ManagerFactory; -import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.zeromq.ZMQ; - -/** - * Manager for publishing messages via JeroMq. - * - * @since 2.6 - */ -public class JeroMqManager extends AbstractManager { - - /** - * System property to enable shutdown hook. - */ - public static final String SYS_PROPERTY_ENABLE_SHUTDOWN_HOOK = "log4j.jeromq.enableShutdownHook"; - - /** - * System property to control JeroMQ I/O thread count. - */ - public static final String SYS_PROPERTY_IO_THREADS = "log4j.jeromq.ioThreads"; - - private static final JeroMqManagerFactory FACTORY = new JeroMqManagerFactory(); - private static final ZMQ.Context CONTEXT; - - static { - LOGGER.trace("JeroMqManager using ZMQ version {}", ZMQ.getVersionString()); - - final int ioThreads = PropertiesUtil.getProperties().getIntegerProperty(SYS_PROPERTY_IO_THREADS, 1); - LOGGER.trace("JeroMqManager creating ZMQ context with ioThreads = {}", ioThreads); - CONTEXT = ZMQ.context(ioThreads); - - final boolean enableShutdownHook = PropertiesUtil.getProperties().getBooleanProperty( - SYS_PROPERTY_ENABLE_SHUTDOWN_HOOK, true); - if (enableShutdownHook) { - ((ShutdownCallbackRegistry) LogManager.getFactory()).addShutdownCallback(new Runnable() { - @Override - public void run() { - CONTEXT.close(); - } - }); - } - } - - private final ZMQ.Socket publisher; - - private JeroMqManager(final String name, final JeroMqConfiguration config) { - super(null, name); - publisher = CONTEXT.socket(ZMQ.PUB); - publisher.setAffinity(config.affinity); - publisher.setBacklog(config.backlog); - publisher.setDelayAttachOnConnect(config.delayAttachOnConnect); - if (config.identity != null) { - publisher.setIdentity(config.identity); - } - publisher.setIPv4Only(config.ipv4Only); - publisher.setLinger(config.linger); - publisher.setMaxMsgSize(config.maxMsgSize); - publisher.setRcvHWM(config.rcvHwm); - publisher.setReceiveBufferSize(config.receiveBufferSize); - publisher.setReceiveTimeOut(config.receiveTimeOut); - publisher.setReconnectIVL(config.reconnectIVL); - publisher.setReconnectIVLMax(config.reconnectIVLMax); - publisher.setSendBufferSize(config.sendBufferSize); - publisher.setSendTimeOut(config.sendTimeOut); - publisher.setSndHWM(config.sndHwm); - publisher.setTCPKeepAlive(config.tcpKeepAlive); - publisher.setTCPKeepAliveCount(config.tcpKeepAliveCount); - publisher.setTCPKeepAliveIdle(config.tcpKeepAliveIdle); - publisher.setTCPKeepAliveInterval(config.tcpKeepAliveInterval); - publisher.setXpubVerbose(config.xpubVerbose); - for (final String endpoint : config.endpoints) { - publisher.bind(endpoint); - } - LOGGER.debug("Created JeroMqManager with {}", config); - } - - public boolean send(final byte[] data) { - return publisher.send(data); - } - - @Override - protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - publisher.close(); - return true; - } - - public static JeroMqManager getJeroMqManager(final String name, 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, - final List endpoints) { - return getManager(name, FACTORY, - new JeroMqConfiguration(affinity, backlog, delayAttachOnConnect, identity, ipv4Only, linger, maxMsgSize, - rcvHwm, receiveBufferSize, receiveTimeOut, reconnectIVL, reconnectIVLMax, sendBufferSize, sendTimeOut, - sndHwm, tcpKeepAlive, tcpKeepAliveCount, tcpKeepAliveIdle, tcpKeepAliveInterval, xpubVerbose, - endpoints)); - } - - public static ZMQ.Context getContext() { - return CONTEXT; - } - - private static class JeroMqConfiguration { - private final long affinity; - private final long backlog; - private final boolean delayAttachOnConnect; - private final byte[] identity; - private final boolean ipv4Only; - private final long linger; - private final long maxMsgSize; - private final long rcvHwm; - private final long receiveBufferSize; - private final int receiveTimeOut; - private final long reconnectIVL; - private final long reconnectIVLMax; - private final long sendBufferSize; - private final int sendTimeOut; - private final long sndHwm; - private final int tcpKeepAlive; - private final long tcpKeepAliveCount; - private final long tcpKeepAliveIdle; - private final long tcpKeepAliveInterval; - private final boolean xpubVerbose; - private final List endpoints; - - private JeroMqConfiguration(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, - final List endpoints) { - this.affinity = affinity; - this.backlog = backlog; - this.delayAttachOnConnect = delayAttachOnConnect; - this.identity = identity; - this.ipv4Only = ipv4Only; - this.linger = linger; - this.maxMsgSize = maxMsgSize; - this.rcvHwm = rcvHwm; - this.receiveBufferSize = receiveBufferSize; - this.receiveTimeOut = receiveTimeOut; - this.reconnectIVL = reconnectIVL; - this.reconnectIVLMax = reconnectIVLMax; - this.sendBufferSize = sendBufferSize; - this.sendTimeOut = sendTimeOut; - this.sndHwm = sndHwm; - this.tcpKeepAlive = tcpKeepAlive; - this.tcpKeepAliveCount = tcpKeepAliveCount; - this.tcpKeepAliveIdle = tcpKeepAliveIdle; - this.tcpKeepAliveInterval = tcpKeepAliveInterval; - this.xpubVerbose = xpubVerbose; - this.endpoints = endpoints; - } - - @Override - public String toString() { - return "JeroMqConfiguration{" + - "affinity=" + affinity + - ", backlog=" + backlog + - ", delayAttachOnConnect=" + delayAttachOnConnect + - ", identity=" + Arrays.toString(identity) + - ", ipv4Only=" + ipv4Only + - ", linger=" + linger + - ", maxMsgSize=" + maxMsgSize + - ", rcvHwm=" + rcvHwm + - ", receiveBufferSize=" + receiveBufferSize + - ", receiveTimeOut=" + receiveTimeOut + - ", reconnectIVL=" + reconnectIVL + - ", reconnectIVLMax=" + reconnectIVLMax + - ", sendBufferSize=" + sendBufferSize + - ", sendTimeOut=" + sendTimeOut + - ", sndHwm=" + sndHwm + - ", tcpKeepAlive=" + tcpKeepAlive + - ", tcpKeepAliveCount=" + tcpKeepAliveCount + - ", tcpKeepAliveIdle=" + tcpKeepAliveIdle + - ", tcpKeepAliveInterval=" + tcpKeepAliveInterval + - ", xpubVerbose=" + xpubVerbose + - ", endpoints=" + endpoints + - '}'; - } - } - - private static class JeroMqManagerFactory implements ManagerFactory { - @Override - public JeroMqManager createManager(final String name, final JeroMqConfiguration data) { - return new JeroMqManager(name, data); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.util.Cancellable; +import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.zeromq.SocketType; +import org.zeromq.ZContext; +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Socket; +import org.zeromq.ZMonitor; +import org.zeromq.ZMonitor.Event; +import org.zeromq.ZMonitor.ZEvent; + +/** + * Manager for publishing messages via JeroMq. + * + * @since 2.6 + */ +public class JeroMqManager extends AbstractManager { + + /** + * System property to enable shutdown hook. + */ + public static final String SYS_PROPERTY_ENABLE_SHUTDOWN_HOOK = "log4j.jeromq.enableShutdownHook"; + + /** + * System property to control JeroMQ I/O thread count. + */ + public static final String SYS_PROPERTY_IO_THREADS = "log4j.jeromq.ioThreads"; + + private static final JeroMqManagerFactory FACTORY = new JeroMqManagerFactory(); + private static final ZContext CONTEXT; + + // Retained to avoid garbage collection of the hook + private static final Cancellable SHUTDOWN_HOOK; + + static { + LOGGER.trace("JeroMqManager using ZMQ version {}", ZMQ.getVersionString()); + + final int ioThreads = PropertiesUtil.getProperties().getIntegerProperty(SYS_PROPERTY_IO_THREADS, 1); + LOGGER.trace("JeroMqManager creating ZMQ context with ioThreads = {}", ioThreads); + CONTEXT = new ZContext(ioThreads); + + final boolean enableShutdownHook = + PropertiesUtil.getProperties().getBooleanProperty(SYS_PROPERTY_ENABLE_SHUTDOWN_HOOK, true); + if (enableShutdownHook && LogManager.getFactory() instanceof ShutdownCallbackRegistry) { + SHUTDOWN_HOOK = ((ShutdownCallbackRegistry) LogManager.getFactory()).addShutdownCallback(CONTEXT::close); + } else { + SHUTDOWN_HOOK = null; + } + } + + private final ZMQ.Socket publisher; + private final List endpoints; + + private JeroMqManager(final String name, final JeroMqConfiguration config) { + super(null, name); + publisher = CONTEXT.createSocket(SocketType.PUB); + final ZMonitor monitor = new ZMonitor(CONTEXT, publisher); + monitor.add(Event.LISTENING); + monitor.start(); + publisher.setAffinity(config.affinity); + publisher.setBacklog(config.backlog); + publisher.setDelayAttachOnConnect(config.delayAttachOnConnect); + if (config.identity != null) { + publisher.setIdentity(config.identity); + } + publisher.setIPv4Only(config.ipv4Only); + publisher.setLinger(config.linger); + publisher.setMaxMsgSize(config.maxMsgSize); + publisher.setRcvHWM(config.rcvHwm); + publisher.setReceiveBufferSize(config.receiveBufferSize); + publisher.setReceiveTimeOut(config.receiveTimeOut); + publisher.setReconnectIVL(config.reconnectIVL); + publisher.setReconnectIVLMax(config.reconnectIVLMax); + publisher.setSendBufferSize(config.sendBufferSize); + publisher.setSendTimeOut(config.sendTimeOut); + publisher.setSndHWM(config.sndHwm); + publisher.setTCPKeepAlive(config.tcpKeepAlive); + publisher.setTCPKeepAliveCount(config.tcpKeepAliveCount); + publisher.setTCPKeepAliveIdle(config.tcpKeepAliveIdle); + publisher.setTCPKeepAliveInterval(config.tcpKeepAliveInterval); + publisher.setXpubVerbose(config.xpubVerbose); + final List endpoints = new ArrayList(config.endpoints.size()); + for (final String endpoint : config.endpoints) { + publisher.bind(endpoint); + // Retrieve the standardized list of endpoints, + // this also converts port 0 to an ephemeral port. + final ZEvent event = monitor.nextEvent(); + endpoints.add(event.address); + } + this.endpoints = Collections.unmodifiableList(endpoints); + monitor.destroy(); + LOGGER.debug("Created JeroMqManager with {}", config); + } + + public boolean send(final byte[] data) { + return publisher.send(data); + } + + @Override + protected boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + publisher.close(); + return true; + } + + // not public, handy for testing + Socket getSocket() { + return publisher; + } + + public List getEndpoints() { + return endpoints; + } + + public static JeroMqManager getJeroMqManager( + final String name, + 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, + final List endpoints) { + return getManager( + name, + FACTORY, + new JeroMqConfiguration( + affinity, + backlog, + delayAttachOnConnect, + identity, + ipv4Only, + linger, + maxMsgSize, + rcvHwm, + receiveBufferSize, + receiveTimeOut, + reconnectIVL, + reconnectIVLMax, + sendBufferSize, + sendTimeOut, + sndHwm, + tcpKeepAlive, + tcpKeepAliveCount, + tcpKeepAliveIdle, + tcpKeepAliveInterval, + xpubVerbose, + endpoints)); + } + + public static ZMQ.Context getContext() { + return CONTEXT.getContext(); + } + + public static ZContext getZContext() { + return CONTEXT; + } + + private static final class JeroMqConfiguration { + private final long affinity; + private final long backlog; + private final boolean delayAttachOnConnect; + private final byte[] identity; + private final boolean ipv4Only; + private final long linger; + private final long maxMsgSize; + private final long rcvHwm; + private final long receiveBufferSize; + private final int receiveTimeOut; + private final long reconnectIVL; + private final long reconnectIVLMax; + private final long sendBufferSize; + private final int sendTimeOut; + private final long sndHwm; + private final int tcpKeepAlive; + private final long tcpKeepAliveCount; + private final long tcpKeepAliveIdle; + private final long tcpKeepAliveInterval; + private final boolean xpubVerbose; + private final List endpoints; + + private JeroMqConfiguration( + 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, + final List endpoints) { + this.affinity = affinity; + this.backlog = backlog; + this.delayAttachOnConnect = delayAttachOnConnect; + this.identity = identity; + this.ipv4Only = ipv4Only; + this.linger = linger; + this.maxMsgSize = maxMsgSize; + this.rcvHwm = rcvHwm; + this.receiveBufferSize = receiveBufferSize; + this.receiveTimeOut = receiveTimeOut; + this.reconnectIVL = reconnectIVL; + this.reconnectIVLMax = reconnectIVLMax; + this.sendBufferSize = sendBufferSize; + this.sendTimeOut = sendTimeOut; + this.sndHwm = sndHwm; + this.tcpKeepAlive = tcpKeepAlive; + this.tcpKeepAliveCount = tcpKeepAliveCount; + this.tcpKeepAliveIdle = tcpKeepAliveIdle; + this.tcpKeepAliveInterval = tcpKeepAliveInterval; + this.xpubVerbose = xpubVerbose; + this.endpoints = endpoints; + } + + @Override + public String toString() { + return "JeroMqConfiguration{" + "affinity=" + + affinity + ", backlog=" + + backlog + ", delayAttachOnConnect=" + + delayAttachOnConnect + ", identity=" + + Arrays.toString(identity) + ", ipv4Only=" + + ipv4Only + ", linger=" + + linger + ", maxMsgSize=" + + maxMsgSize + ", rcvHwm=" + + rcvHwm + ", receiveBufferSize=" + + receiveBufferSize + ", receiveTimeOut=" + + receiveTimeOut + ", reconnectIVL=" + + reconnectIVL + ", reconnectIVLMax=" + + reconnectIVLMax + ", sendBufferSize=" + + sendBufferSize + ", sendTimeOut=" + + sendTimeOut + ", sndHwm=" + + sndHwm + ", tcpKeepAlive=" + + tcpKeepAlive + ", tcpKeepAliveCount=" + + tcpKeepAliveCount + ", tcpKeepAliveIdle=" + + tcpKeepAliveIdle + ", tcpKeepAliveInterval=" + + tcpKeepAliveInterval + ", xpubVerbose=" + + xpubVerbose + ", endpoints=" + + endpoints + '}'; + } + } + + private static class JeroMqManagerFactory implements ManagerFactory { + @Override + public JeroMqManager createManager(final String name, final JeroMqConfiguration data) { + return new JeroMqManager(name, data); + } + } +} 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 index b1df1e7724d..fe654885b51 100644 --- 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 @@ -20,4 +20,9 @@ * * @since 2.4 */ +@Export +@Version("2.21.0") package org.apache.logging.log4j.core.appender.mom.jeromq; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/DefaultKafkaProducerFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/DefaultKafkaProducerFactory.java index e0b39219dbc..165f40471d0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/DefaultKafkaProducerFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/DefaultKafkaProducerFactory.java @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.appender.mom.kafka; import java.util.Properties; - import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; @@ -29,7 +27,7 @@ public class DefaultKafkaProducerFactory implements KafkaProducerFactory { /** * Creates a new Kafka Producer from the given configuration properties. - * + * * @param config * Kafka Producer configuration * properties. @@ -39,5 +37,4 @@ public class DefaultKafkaProducerFactory implements KafkaProducerFactory { public Producer newKafkaProducer(final Properties config) { return new KafkaProducer<>(config); } - } 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 index f7d50af4b8a..a9788a2a59b 100644 --- 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.mom.kafka; import java.io.Serializable; @@ -22,7 +21,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - +import java.util.stream.Stream; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; @@ -35,8 +34,8 @@ 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; +import org.apache.logging.log4j.core.util.Integers; /** * Sends log events to an Apache Kafka topic. @@ -46,22 +45,27 @@ public final class KafkaAppender extends AbstractAppender { /** * Builds KafkaAppender instances. - * @param The type to build + * + * @param + * The type to build */ public static class Builder> extends AbstractAppender.Builder implements org.apache.logging.log4j.core.util.Builder { - @PluginAttribute("topic") + @PluginAttribute("retryCount") + private int retryCount; + + @PluginAttribute("topic") private String topic; @PluginAttribute("key") private String key; - + @PluginAttribute(value = "syncSend", defaultBoolean = true) private boolean syncSend; - @PluginElement("Properties") - private Property[] properties; + @PluginAttribute(value = "sendEventTimestamp", defaultBoolean = false) + private boolean sendEventTimestamp; @SuppressWarnings("resource") @Override @@ -71,25 +75,64 @@ public KafkaAppender build() { 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); + final KafkaManager kafkaManager = KafkaManager.getManager( + getConfiguration().getLoggerContext(), + getName(), + topic, + syncSend, + sendEventTimestamp, + getPropertyArray(), + key); + return new KafkaAppender( + getName(), + layout, + getFilter(), + isIgnoreExceptions(), + kafkaManager, + getPropertyArray(), + getRetryCount()); + } + + public Integer getRetryCount() { + Integer intRetryCount = null; + try { + intRetryCount = Integer.valueOf(retryCount); + } catch (NumberFormatException e) { + + } + return intRetryCount; } public String getTopic() { return topic; } + public boolean isSendEventTimestamp() { + return sendEventTimestamp; + } + public boolean isSyncSend() { return syncSend; } - public Property[] getProperties() { - return properties; + public B setKey(final String key) { + this.key = key; + return asBuilder(); } - public B setTopic(final String topic) { - this.topic = topic; + @Deprecated + public B setRetryCount(final String retryCount) { + this.retryCount = Integers.parseInt(retryCount, 0); + return asBuilder(); + } + + public B setRetryCount(final int retryCount) { + this.retryCount = retryCount; + return asBuilder(); + } + + public B setSendEventTimestamp(final boolean sendEventTimestamp) { + this.sendEventTimestamp = sendEventTimestamp; return asBuilder(); } @@ -98,12 +141,15 @@ public B setSyncSend(final boolean syncSend) { return asBuilder(); } - public B setProperties(final Property[] properties) { - this.properties = properties; + public B setTopic(final String topic) { + this.topic = topic; return asBuilder(); } } - + + private static final String[] KAFKA_CLIENT_PACKAGES = + new String[] {"org.apache.kafka.common", "org.apache.kafka.clients"}; + @Deprecated public static KafkaAppender createAppender( final Layout layout, @@ -120,12 +166,24 @@ public static KafkaAppender createAppender( return null; } final KafkaManager kafkaManager = - new KafkaManager(configuration.getLoggerContext(), name, topic, true, properties, key); - return new KafkaAppender(name, layout, filter, ignoreExceptions, kafkaManager); + KafkaManager.getManager(configuration.getLoggerContext(), name, topic, true, properties, key); + return new KafkaAppender(name, layout, filter, ignoreExceptions, kafkaManager, null, 0); + } + + /** + * Tests if the given log event is from a Kafka Producer implementation. + * + * @param event The event to test. + * @return true to avoid recursion and skip logging, false to log. + */ + private static boolean isRecursive(final LogEvent event) { + return Stream.of(KAFKA_CLIENT_PACKAGES) + .anyMatch(prefix -> event.getLoggerName().startsWith(prefix)); } /** * Creates a builder for a KafkaAppender. + * * @return a builder for a KafkaAppender. */ @PluginBuilderFactory @@ -133,42 +191,49 @@ public static > B newBuilder() { return new Builder().asBuilder(); } + private final Integer retryCount; + 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); + private KafkaAppender( + final String name, + final Layout layout, + final Filter filter, + final boolean ignoreExceptions, + final KafkaManager manager, + final Property[] properties, + final int retryCount) { + super(name, filter, layout, ignoreExceptions, properties); this.manager = Objects.requireNonNull(manager, "manager"); + this.retryCount = retryCount; } @Override public void append(final LogEvent event) { - if (event.getLoggerName() != null && event.getLoggerName().startsWith("org.apache.kafka")) { + if (event.getLoggerName() != null && isRecursive(event)) { LOGGER.warn("Recursive logging from [{}] for appender [{}].", event.getLoggerName(), getName()); } else { try { tryAppend(event); } catch (final Exception e) { + + if (this.retryCount != null) { + int currentRetryAttempt = 0; + while (currentRetryAttempt < this.retryCount) { + currentRetryAttempt++; + try { + tryAppend(event); + break; + } catch (Exception e1) { + + } + } + } 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(); @@ -186,10 +251,21 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { @Override public String toString() { - return "KafkaAppender{" + - "name=" + getName() + - ", state=" + getState() + - ", topic=" + manager.getTopic() + - '}'; + return "KafkaAppender{" + "name=" + getName() + ", state=" + getState() + ", topic=" + manager.getTopic() + '}'; + } + + 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, event.getTimeMillis()); } } 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 index 9af11cd6931..012e8837c67 100644 --- 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.mom.kafka; import java.nio.charset.StandardCharsets; @@ -24,14 +23,16 @@ 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.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.ByteArraySerializer; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Log4jThread; public class KafkaManager extends AbstractManager { @@ -50,22 +51,52 @@ public class KafkaManager extends AbstractManager { private final String topic; private final String key; private final boolean syncSend; + private final boolean sendTimestamp; - public KafkaManager(final LoggerContext loggerContext, final String name, final String topic, final boolean syncSend, - final Property[] properties, final String key) { + private static final KafkaManagerFactory factory = new KafkaManagerFactory(); + + /* + * The Constructor should have been declared private as all Managers are create + * by the internal factory; + */ + public KafkaManager( + final LoggerContext loggerContext, + final String name, + final String topic, + final boolean syncSend, + final Property[] properties, + final String key) { + this(loggerContext, name, topic, syncSend, false, properties, key); + } + + private KafkaManager( + final LoggerContext loggerContext, + final String name, + final String topic, + final boolean syncSend, + final boolean sendTimestamp, + 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"); + this.sendTimestamp = sendTimestamp; + + config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); + config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); + config.put(ProducerConfig.BATCH_SIZE_CONFIG, 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)); + String timeoutMillis = config.getProperty("timeout.ms"); + if (timeoutMillis == null) { + timeoutMillis = config.getProperty(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, DEFAULT_TIMEOUT_MILLIS); + } + this.timeoutMillis = Integers.parseInt(timeoutMillis); } @Override @@ -80,15 +111,15 @@ public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { 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"); + // This thread is a workaround for this Kafka issue: + // https://issues.apache.org/jira/browse/KAFKA-1660 + final Thread closeThread = new Log4jThread( + () -> { + if (producer != null) { + producer.close(); + } + }, + "KafkaManager-CloseThread"); closeThread.setDaemon(true); // avoid blocking JVM shutdown closeThread.start(); try { @@ -100,27 +131,36 @@ public void run() { } } + @Deprecated public void send(final byte[] msg) throws ExecutionException, InterruptedException, TimeoutException { + send(msg, null); + } + + public void send(final byte[] msg, final Long eventTimestamp) + 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); + 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); + final Long timestamp = sendTimestamp ? eventTimestamp : null; + + final ProducerRecord newRecord = new ProducerRecord<>(topic, null, timestamp, 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); - } + producer.send(newRecord, (metadata, e) -> { + if (e != null) { + LOGGER.error("Unable to write to Kafka in appender [" + getName() + "]", e); } }); } @@ -128,11 +168,74 @@ public void onCompletion(final RecordMetadata metadata, final Exception e) { } public void startup() { - producer = producerFactory.newKafkaProducer(config); + if (producer == null) { + producer = producerFactory.newKafkaProducer(config); + } } public String getTopic() { return topic; } + @Deprecated + public static KafkaManager getManager( + final LoggerContext loggerContext, + final String name, + final String topic, + final boolean syncSend, + final Property[] properties, + final String key) { + return getManager(loggerContext, name, topic, syncSend, false, properties, key); + } + + static KafkaManager getManager( + final LoggerContext loggerContext, + final String name, + final String topic, + final boolean syncSend, + final boolean sendTimestamp, + final Property[] properties, + final String key) { + final StringBuilder sb = new StringBuilder(name); + sb.append(" ").append(topic).append(" ").append(syncSend).append(" ").append(sendTimestamp); + for (Property prop : properties) { + sb.append(" ").append(prop.getName()).append("=").append(prop.getValue()); + } + return getManager( + sb.toString(), + factory, + new FactoryData(loggerContext, topic, syncSend, sendTimestamp, properties, key)); + } + + private static class FactoryData { + private final LoggerContext loggerContext; + private final String topic; + private final boolean syncSend; + private final boolean sendTimestamp; + private final Property[] properties; + private final String key; + + public FactoryData( + final LoggerContext loggerContext, + final String topic, + final boolean syncSend, + final boolean sendTimestamp, + final Property[] properties, + final String key) { + this.loggerContext = loggerContext; + this.topic = topic; + this.syncSend = syncSend; + this.sendTimestamp = sendTimestamp; + this.properties = properties; + this.key = key; + } + } + + private static class KafkaManagerFactory implements ManagerFactory { + @Override + public KafkaManager createManager(final String name, final FactoryData data) { + return new KafkaManager( + data.loggerContext, name, data.topic, data.syncSend, data.sendTimestamp, data.properties, data.key); + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaProducerFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaProducerFactory.java index 7532bb84afb..4ed93ef70ef 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaProducerFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaProducerFactory.java @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.appender.mom.kafka; import java.util.Properties; - import org.apache.kafka.clients.producer.Producer; /** @@ -28,12 +26,11 @@ public interface KafkaProducerFactory { /** * Creates a new Kafka Producer from the given configuration properties. - * + * * @param config * Kafka Producer configuration * properties. * @return a new Kafka {@link Producer}. */ Producer newKafkaProducer(Properties config); - -} \ No newline at end of file +} 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 index 60f4dcf09e7..ed6d4c3b9a4 100644 --- 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 @@ -20,4 +20,9 @@ * * @since 2.4 */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.core.appender.mom.kafka; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 index 866b689c777..c12d8343757 100644 --- 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 @@ -20,4 +20,9 @@ * * @since 2.1 */ +@Export +@Version("2.25.0") package org.apache.logging.log4j.core.appender.mom; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..43fb66ad3bd 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.nosql; import java.util.concurrent.atomic.AtomicBoolean; @@ -29,7 +28,7 @@ */ public abstract class AbstractNoSqlConnection> implements NoSqlConnection { - private final AtomicBoolean closed = new AtomicBoolean(false); + private final AtomicBoolean closed = new AtomicBoolean(); @Override public void close() { @@ -44,5 +43,4 @@ public void close() { public boolean isClosed() { return this.closed.get(); } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java index 7600e1a5167..203cbc05113 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/DefaultNoSqlObject.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.nosql; @@ -31,6 +31,9 @@ public class DefaultNoSqlObject implements NoSqlObject> { private final Map map; + /** + * Constructs a new instance. + */ public DefaultNoSqlObject() { this.map = new HashMap<>(); } @@ -42,21 +45,25 @@ public void set(final String field, final Object value) { @Override public void set(final String field, final NoSqlObject> value) { - this.map.put(field, value.unwrap()); + this.map.put(field, value != null ? value.unwrap() : null); } @Override public void set(final String field, final Object[] values) { - this.map.put(field, Arrays.asList(values)); + this.map.put(field, values != null ? Arrays.asList(values) : null); } @Override public void set(final String field, final NoSqlObject>[] values) { - final List> list = new ArrayList<>(values.length); - for (final NoSqlObject> value : values) { - list.add(value.unwrap()); + if (values == null) { + this.map.put(field, null); + } else { + final List> list = new ArrayList<>(values.length); + for (final NoSqlObject> value : values) { + list.add(value.unwrap()); + } + this.map.put(field, list); } - this.map.put(field, list); } @Override 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..917eb4301be 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 @@ -1,33 +1,34 @@ /* * 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.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.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.util.Booleans; +import org.apache.logging.log4j.core.util.KeyValuePair; /** * This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires @@ -37,7 +38,7 @@ * 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 @@ -47,7 +48,7 @@ public final class NoSqlAppender extends AbstractDatabaseAppender * The type to build */ @@ -60,6 +61,9 @@ public static class Builder> extends AbstractAppender.Build @PluginElement("NoSqlProvider") private NoSqlProvider provider; + @PluginElement("AdditionalField") + private KeyValuePair[] additionalFields; + @SuppressWarnings("resource") @Override public NoSqlAppender build() { @@ -71,37 +75,36 @@ public NoSqlAppender build() { final String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSize + ", provider=" + provider + " }"; - - final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, - bufferSize, provider); + final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager( + managerName, bufferSize, provider, additionalFields, getConfiguration()); if (manager == null) { 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(); } @@ -123,18 +126,18 @@ public B setProvider(NoSqlProvider provider) { * @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}. + * @deprecated since 2.11.0; use {@link Builder}. */ @SuppressWarnings("resource") @Deprecated public static NoSqlAppender createAppender( - // @formatter:off + // @formatter:off final String name, - final String ignore, + final String ignore, final Filter filter, final String bufferSize, final NoSqlProvider provider) { - // @formatter:on + // @formatter:on if (provider == null) { LOGGER.error("NoSQL provider not specified for appender [{}].", name); return null; @@ -143,16 +146,16 @@ public static NoSqlAppender createAppender( 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 String managerName = + "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt + ", provider=" + provider + " }"; - final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt, - provider); + final NoSqlDatabaseManager manager = + NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt, provider, null, null); if (manager == null) { return null; } - return new NoSqlAppender(name, filter, null, ignoreExceptions, manager); + return new NoSqlAppender(name, filter, null, ignoreExceptions, null, manager); } @PluginBuilderFactory @@ -162,9 +165,14 @@ public static > B newBuilder() { 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/NoSqlConnection.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java index 4ca024e583d..97835d69d86 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlConnection.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.nosql; 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..e053944a0c8 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 @@ -1,34 +1,35 @@ /* * 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.logging.log4j.core.appender.nosql; import java.io.Serializable; - -import javax.jms.JMSException; - +import java.util.Objects; +import java.util.stream.Stream; 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.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.config.Configuration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.MapMessage; -import org.apache.logging.log4j.util.BiConsumer; import org.apache.logging.log4j.util.ReadOnlyStringMap; /** @@ -37,27 +38,115 @@ * @param A type parameter for reassuring the compiler that all operations are using the same {@link NoSqlObject}. */ public final class NoSqlDatabaseManager extends AbstractDatabaseManager { + /** + * Encapsulates data that {@link NoSQLDatabaseManagerFactory} uses to create managers. + */ + private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { + private final NoSqlProvider provider; + private final KeyValuePair[] additionalFields; + + protected FactoryData( + final Configuration configuration, + final int bufferSize, + final NoSqlProvider provider, + final KeyValuePair[] additionalFields) { + super(configuration, bufferSize, null); // no layout + this.provider = Objects.requireNonNull(provider, "provider"); + this.additionalFields = additionalFields; // null OK + } + } + + /** + * Creates managers. + */ + private static final class NoSQLDatabaseManagerFactory + implements ManagerFactory, FactoryData> { + @Override + @SuppressWarnings("unchecked") + public NoSqlDatabaseManager createManager(final String name, final FactoryData data) { + Objects.requireNonNull(data, "data"); + return new NoSqlDatabaseManager( + name, data.getBufferSize(), data.provider, data.additionalFields, data.getConfiguration()); + } + } + private static final NoSQLDatabaseManagerFactory FACTORY = new NoSQLDatabaseManagerFactory(); + /** + * Creates a NoSQL manager for use within the {@link NoSqlAppender}, 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 provider A provider instance which will be used to obtain connections to the chosen NoSQL database. + * @return a new or existing NoSQL manager as applicable. + * @deprecated Use {@link #getNoSqlDatabaseManager(String, int, NoSqlProvider, KeyValuePair[], Configuration)}. + */ + @Deprecated + public static NoSqlDatabaseManager getNoSqlDatabaseManager( + final String name, final int bufferSize, final NoSqlProvider provider) { + return AbstractDatabaseManager.getManager(name, new FactoryData(null, bufferSize, provider, null), FACTORY); + } + + /** + * Creates a NoSQL manager for use within the {@link NoSqlAppender}, 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 provider A provider instance which will be used to obtain connections to the chosen NoSQL database. + * @param additionalFields Additional fields. + * @param configuration TODO + * @return a new or existing NoSQL manager as applicable. + */ + public static NoSqlDatabaseManager getNoSqlDatabaseManager( + final String name, + final int bufferSize, + final NoSqlProvider provider, + final KeyValuePair[] additionalFields, + final Configuration configuration) { + return AbstractDatabaseManager.getManager( + name, new FactoryData(configuration, bufferSize, provider, additionalFields), FACTORY); + } + private final NoSqlProvider>> provider; private NoSqlConnection> connection; - private NoSqlDatabaseManager(final String name, final int bufferSize, - final NoSqlProvider>> provider) { - super(name, bufferSize); + private final KeyValuePair[] additionalFields; + + private NoSqlDatabaseManager( + final String name, + final int bufferSize, + final NoSqlProvider>> provider, + final KeyValuePair[] additionalFields, + final Configuration configuration) { + super(name, bufferSize, null, configuration); this.provider = provider; + this.additionalFields = additionalFields; } - @Override - protected void startupInternal() { - // nothing to see here + private NoSqlObject buildMarkerEntity(final Marker marker) { + final NoSqlObject entity = this.connection.createObject(); + entity.set("name", marker.getName()); + + final Marker[] parents = marker.getParents(); + if (parents != null) { + @SuppressWarnings("unchecked") + final NoSqlObject[] parentEntities = new NoSqlObject[parents.length]; + for (int i = 0; i < parents.length; i++) { + parentEntities[i] = buildMarkerEntity(parents[i]); + } + entity.set("parents", parentEntities); + } + return entity; } @Override - protected boolean shutdownInternal() { - // NoSQL doesn't use transactions, so all we need to do here is simply close the client - return Closer.closeSilently(this.connection); + protected boolean commitAndClose() { + // all NoSQL drivers auto-commit (since NoSQL doesn't generally use the concept of transactions). + // also, all our NoSQL drivers use internal connection pooling and provide clients, not connections. + // thus, we should not be closing the client until shutdown as NoSQL is very different from SQL. + // see LOG4J2-591 and LOG4J2-676 + return true; } @Override @@ -69,43 +158,40 @@ 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()) { - throw new AppenderLoggingException( - "Cannot write logging event; NoSQL manager not connected to the database."); - } - - final NoSqlObject entity = this.connection.createObject(); - if (serializable instanceof MapMessage) { - setFields((MapMessage) serializable, entity); - } else { - setFields(event, entity); + private NoSqlObject[] convertStackTrace(final StackTraceElement[] stackTrace) { + final NoSqlObject[] stackTraceEntities = this.connection.createList(stackTrace.length); + for (int i = 0; i < stackTrace.length; i++) { + stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]); } + return stackTraceEntities; + } - this.connection.insertObject(entity); + private NoSqlObject convertStackTraceElement(final StackTraceElement element) { + final NoSqlObject elementEntity = this.connection.createObject(); + elementEntity.set("className", element.getClassName()); + elementEntity.set("methodName", element.getMethodName()); + elementEntity.set("fileName", element.getFileName()); + elementEntity.set("lineNumber", element.getLineNumber()); + return elementEntity; } - 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); - } - }); + private void setAdditionalFields(final NoSqlObject entity) { + if (additionalFields != null) { + final NoSqlObject object = connection.createObject(); + final StrSubstitutor strSubstitutor = getStrSubstitutor(); + Stream.of(additionalFields) + .forEach(f -> object.set( + f.getKey(), strSubstitutor != null ? strSubstitutor.replace(f.getValue()) : f.getValue())); + entity.set("additionalFields", object); + } } private void setFields(final LogEvent event, final NoSqlObject entity) { entity.set("level", event.getLevel()); entity.set("loggerName", event.getLoggerName()); - entity.set("message", event.getMessage() == null ? null : event.getMessage().getFormattedMessage()); + entity.set( + "message", + event.getMessage() == null ? null : event.getMessage().getFormattedMessage()); final StackTraceElement source = event.getSource(); if (source == null) { @@ -155,12 +241,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((key, val) -> contextMapEntity.set(key, val)); entity.set("contextMap", contextMapEntity); } @@ -172,82 +253,36 @@ public void accept(final String key, final String val) { } } - private NoSqlObject buildMarkerEntity(final Marker marker) { - final NoSqlObject entity = this.connection.createObject(); - entity.set("name", marker.getName()); - - final Marker[] parents = marker.getParents(); - if (parents != null) { - @SuppressWarnings("unchecked") - final NoSqlObject[] parentEntities = new NoSqlObject[parents.length]; - for (int i = 0; i < parents.length; i++) { - parentEntities[i] = buildMarkerEntity(parents[i]); - } - entity.set("parents", parentEntities); - } - return entity; + 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((key, value) -> noSqlObject.set(key, value)); } @Override - protected boolean commitAndClose() { - // all NoSQL drivers auto-commit (since NoSQL doesn't generally use the concept of transactions). - // also, all our NoSQL drivers use internal connection pooling and provide clients, not connections. - // thus, we should not be closing the client until shutdown as NoSQL is very different from SQL. - // see LOG4J2-591 and LOG4J2-676 - return true; - } - - private NoSqlObject[] convertStackTrace(final StackTraceElement[] stackTrace) { - final NoSqlObject[] stackTraceEntities = this.connection.createList(stackTrace.length); - for (int i = 0; i < stackTrace.length; i++) { - stackTraceEntities[i] = this.convertStackTraceElement(stackTrace[i]); - } - return stackTraceEntities; - } - - private NoSqlObject convertStackTraceElement(final StackTraceElement element) { - final NoSqlObject elementEntity = this.connection.createObject(); - elementEntity.set("className", element.getClassName()); - elementEntity.set("methodName", element.getMethodName()); - elementEntity.set("fileName", element.getFileName()); - elementEntity.set("lineNumber", element.getLineNumber()); - return elementEntity; + protected boolean shutdownInternal() { + // NoSQL doesn't use transactions, so all we need to do here is simply close the client + return Closer.closeSilently(this.connection); } - /** - * Creates a NoSQL manager for use within the {@link NoSqlAppender}, 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 provider A provider instance which will be used to obtain connections to the chosen NoSQL database. - * @return a new or existing NoSQL manager as applicable. - */ - public static NoSqlDatabaseManager getNoSqlDatabaseManager(final String name, final int bufferSize, - final NoSqlProvider provider) { - return AbstractDatabaseManager.getManager(name, new FactoryData(bufferSize, provider), FACTORY); + @Override + protected void startupInternal() { + // nothing to see here } - /** - * Encapsulates data that {@link NoSQLDatabaseManagerFactory} uses to create managers. - */ - private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final NoSqlProvider provider; - - protected FactoryData(final int bufferSize, final NoSqlProvider provider) { - super(bufferSize, null); - this.provider = provider; + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + if (!this.isRunning() || this.connection == null || this.connection.isClosed()) { + throw new AppenderLoggingException( + "Cannot write logging event; NoSQL manager not connected to the database."); } - } - /** - * Creates managers. - */ - private static final class NoSQLDatabaseManagerFactory implements - ManagerFactory, FactoryData> { - @Override - @SuppressWarnings("unchecked") - public NoSqlDatabaseManager createManager(final String name, final FactoryData data) { - return new NoSqlDatabaseManager(name, data.getBufferSize(), data.provider); + final NoSqlObject entity = this.connection.createObject(); + if (serializable instanceof MapMessage) { + setFields((MapMessage) serializable, entity); + } else { + setFields(event, entity); } + setAdditionalFields(entity); + this.connection.insertObject(entity); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java index 41bc7f91335..5662b712b31 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlObject.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.nosql; @@ -22,6 +22,7 @@ * @param Specifies what type of underlying object (such as a MongoDB BasicDBObject) this NoSqlObject wraps. */ public interface NoSqlObject { + /** * Sets the value of a property on this object to a String or primitive. * 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..572a6e14066 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.nosql; @@ -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/nosql/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java index 641762ff351..590123a84f1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/package-info.java @@ -21,4 +21,9 @@ * {@link org.apache.logging.log4j.core.appender.nosql.NoSqlConnection NoSqlConnection}, and * {@link org.apache.logging.log4j.core.appender.nosql.NoSqlProvider NoSqlProvider}. */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.core.appender.nosql; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/package-info.java index d032b8e2292..a18b7715e4f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/package-info.java @@ -17,4 +17,9 @@ /** * Log4j 2 Appenders. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.appender; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..c2d6a4e5044 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 @@ -1,25 +1,25 @@ /* * 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.logging.log4j.core.appender.rewrite; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + 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; @@ -32,15 +32,19 @@ /** * Rewrites log event levels for a given logger name. - * + * * @since 2.4 */ -@Plugin(name = "LoggerNameLevelRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) +@Plugin( + name = "LoggerNameLevelRewritePolicy", + category = Core.CATEGORY_NAME, + elementType = "rewritePolicy", + printObject = true) 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. @@ -53,7 +57,7 @@ public static LoggerNameLevelRewritePolicy createPolicy( // @formatter:off @PluginAttribute("logger") final String loggerNamePrefix, @PluginElement("KeyValuePair") final KeyValuePair[] levelPairs) { - // @formatter:on + // @formatter:on final Map newMap = new HashMap<>(levelPairs.length); for (final KeyValuePair keyValuePair : levelPairs) { newMap.put(getLevel(keyValuePair.getKey()), getLevel(keyValuePair.getValue())); @@ -62,7 +66,7 @@ public static LoggerNameLevelRewritePolicy createPolicy( } private static Level getLevel(final String name) { - return Level.getLevel(name.toUpperCase(Locale.ROOT)); + return Level.getLevel(toRootUpperCase(name)); } private final String loggerName; @@ -70,7 +74,6 @@ private static Level getLevel(final String name) { private final Map map; private LoggerNameLevelRewritePolicy(final String loggerName, final Map map) { - super(); this.loggerName = loggerName; this.map = map; } @@ -85,8 +88,8 @@ public LogEvent rewrite(final LogEvent event) { if (newLevel == null || newLevel == sourceLevel) { return event; } - final LogEvent result = new Log4jLogEvent.Builder(event).setLevel(newLevel).build(); + final LogEvent result = + new Log4jLogEvent.Builder(event).setLevel(newLevel).build(); return result; } - } 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..4848854738a 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 @@ -1,24 +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. */ 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; @@ -37,7 +36,7 @@ */ @Plugin(name = "MapRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) public final class MapRewritePolicy implements RewritePolicy { - + /** * Allow subclasses access to the status logger without creating another instance. */ @@ -90,12 +89,12 @@ public LogEvent rewrite(final LogEvent source) { * keys should be updated. */ public enum Mode { - + /** * Keys should be added. */ Add, - + /** * Keys should be updated. */ @@ -127,8 +126,7 @@ public String toString() { */ @PluginFactory public static MapRewritePolicy createPolicy( - @PluginAttribute("mode") final String mode, - @PluginElement("KeyValuePair") final KeyValuePair[] pairs) { + @PluginAttribute("mode") final String mode, @PluginElement("KeyValuePair") final KeyValuePair[] pairs) { Mode op = mode == null ? op = Mode.Add : Mode.valueOf(mode); if (pairs == null || pairs.length == 0) { LOGGER.error("keys and values must be specified for the MapRewritePolicy"); 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..5e44a6bc1e0 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rewrite; @@ -20,7 +20,6 @@ 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; @@ -38,9 +37,13 @@ /** * 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) +@Plugin( + name = "PropertiesRewritePolicy", + category = Core.CATEGORY_NAME, + elementType = "rewritePolicy", + printObject = true) public final class PropertiesRewritePolicy implements RewritePolicy { - + /** * Allows subclasses access to the status logger without creating another instance. */ @@ -70,8 +73,11 @@ public LogEvent rewrite(final LogEvent source) { final StringMap newContextData = ContextDataFactory.createContextData(source.getContextData()); for (final Map.Entry entry : properties.entrySet()) { final Property prop = entry.getKey(); - newContextData.putValue(prop.getName(), entry.getValue().booleanValue() ? - config.getStrSubstitutor().replace(prop.getValue()) : prop.getValue()); + newContextData.putValue( + prop.getName(), + entry.getValue().booleanValue() + ? config.getStrSubstitutor().replace(prop.getValue()) + : prop.getValue()); } return new Log4jLogEvent.Builder(source).setContextData(newContextData).build(); @@ -101,8 +107,8 @@ public String toString() { * @return The PropertiesRewritePolicy. */ @PluginFactory - public static PropertiesRewritePolicy createPolicy(@PluginConfiguration final Configuration config, - @PluginElement("Properties") final Property[] props) { + public static PropertiesRewritePolicy createPolicy( + @PluginConfiguration final Configuration config, @PluginElement("Properties") final Property[] props) { if (props == null || props.length == 0) { LOGGER.error("Properties must be specified for the PropertiesRewritePolicy"); return null; 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..4cb0fadc55a 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 @@ -1,24 +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. */ 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; @@ -27,11 +26,13 @@ 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.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.LocationAware; import org.apache.logging.log4j.core.util.Booleans; /** @@ -45,10 +46,15 @@ public final class RewriteAppender extends AbstractAppender { private final RewritePolicy rewritePolicy; private final AppenderRef[] appenderRefs; - 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); + private RewriteAppender( + final String name, + final Filter filter, + final boolean ignoreExceptions, + final AppenderRef[] appenderRefs, + final RewritePolicy rewritePolicy, + final Configuration config, + final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.config = config; this.rewritePolicy = rewritePolicy; this.appenderRefs = appenderRefs; @@ -60,8 +66,8 @@ public void start() { final String name = ref.getRef(); final Appender appender = config.getAppender(name); if (appender != null) { - final Filter filter = appender instanceof AbstractAppender ? - ((AbstractAppender) appender).getFilter() : null; + final Filter filter = + appender instanceof AbstractAppender ? ((AbstractAppender) appender).getFilter() : null; appenders.put(name, new AppenderControl(appender, ref.getLevel(), filter)); } else { LOGGER.error("Appender " + ref + " cannot be located. Reference ignored"); @@ -113,6 +119,17 @@ public static RewriteAppender createAppender( LOGGER.error("No appender references defined for RewriteAppender"); return null; } - return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config); + return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config, null); + } + + @Override + public boolean requiresLocation() { + for (final AppenderControl control : appenders.values()) { + final Appender appender = control.getAppender(); + if (appender instanceof LocationAware && ((LocationAware) appender).requiresLocation()) { + return true; + } + } + return false; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewritePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewritePolicy.java index 531fcb5f747..ad604edae5e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewritePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewritePolicy.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rewrite; @@ -21,7 +21,6 @@ /** * Interface to be implemented by components that support modifications to the LogEvent. */ - public interface RewritePolicy { /** * Rewrite a logging event. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/package-info.java index 18375564e95..ba83e90355d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/package-info.java @@ -17,4 +17,9 @@ /** * Apache Flume Appender. Requires the user specifically include Flume and its dependencies. */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.core.appender.rewrite; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..6bc352b993f 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -27,13 +28,13 @@ import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LoggingException; 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.lookup.StrSubstitutor; import org.apache.logging.log4j.core.pattern.NotANumber; +import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.status.StatusLogger; /** @@ -46,13 +47,14 @@ 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) { this.strSubstitutor = strSubstitutor; } - public StrSubstitutor getStrSubstitutor() { return strSubstitutor; } @@ -79,24 +81,33 @@ protected int suffixLength(final String lowFilename) { return 0; } - protected SortedMap getEligibleFiles(final RollingFileManager manager) { return getEligibleFiles(manager, true); } - protected SortedMap getEligibleFiles(final RollingFileManager manager, - final boolean isAscending) { + protected SortedMap getEligibleFiles(final RollingFileManager manager, final boolean isAscending) { 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) { + @Deprecated + protected SortedMap getEligibleFiles( + final String path, final String logfilePattern, final boolean isAscending) { + return getEligibleFiles("", path, logfilePattern, isAscending); + } + + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The file path should be specified in the configuration file.") + 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,29 +116,43 @@ protected SortedMap getEligibleFiles(final String path, final Str } else { parent.mkdirs(); } - if (!logfilePattern.contains("%i")) { + if (!PATTERN_COUNTER.matcher(logfilePattern).find()) { 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)) { - for (final Path entry: stream) { + 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 = Integers.parseInt(matcher.group(1)); + eligibleFiles.put(index, entry); + } catch (NumberFormatException ex) { + LOGGER.debug( + "Ignoring file {} which matches pattern but the index is invalid.", + entry.toFile().getName()); + } } } } catch (final IOException ioe) { throw new LoggingException("Error reading folder " + dir + " " + ioe.getMessage(), ioe); } - return isAscending? eligibleFiles : eligibleFiles.descendingMap(); + return isAscending ? eligibleFiles : eligibleFiles.descendingMap(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractTriggeringPolicy.java index e9f1283113d..94adefba461 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractTriggeringPolicy.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.rolling; import org.apache.logging.log4j.core.AbstractLifeCycle; 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..cce3442ea71 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 @@ -1,24 +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. */ 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; @@ -50,6 +49,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); } } @@ -76,7 +76,7 @@ public boolean isTriggeringEvent(final LogEvent event) { */ @PluginFactory public static CompositeTriggeringPolicy createPolicy( - @PluginElement("Policies") final TriggeringPolicy... triggeringPolicy) { + @PluginElement("Policies") final TriggeringPolicy... triggeringPolicy) { return new CompositeTriggeringPolicy(triggeringPolicy); } @@ -100,5 +100,4 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { public String toString() { return "CompositeTriggeringPolicy(policies=" + Arrays.toString(triggeringPolicies) + ")"; } - } 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..7ec778764e1 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -20,7 +20,6 @@ import java.util.Date; 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; @@ -48,8 +47,8 @@ public final class CronTriggeringPolicy extends AbstractTriggeringPolicy { private volatile Date lastRollDate; private CronScheduledFuture future; - private CronTriggeringPolicy(final CronExpression schedule, final boolean checkOnStartup, - final Configuration configuration) { + private CronTriggeringPolicy( + final CronExpression schedule, final boolean checkOnStartup, final Configuration configuration) { this.cronExpression = Objects.requireNonNull(schedule, "schedule"); this.configuration = Objects.requireNonNull(configuration, "configuration"); this.checkOnStartup = checkOnStartup; @@ -57,7 +56,7 @@ private CronTriggeringPolicy(final CronExpression schedule, final boolean checkO /** * Initializes the policy. - * + * * @param aManager * The RollingFileManager. */ @@ -70,8 +69,11 @@ public void initialize(final RollingFileManager aManager) { aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime()); LOGGER.debug("LastRollForFile {}, LastRegularRole {}", lastRollForFile, lastRegularRoll); aManager.getPatternProcessor().setPrevFileTime(lastRegularRoll.getTime()); - if (checkOnStartup && lastRollForFile != null && lastRegularRoll != null && - lastRollForFile.before(lastRegularRoll)) { + aManager.getPatternProcessor().setTimeBased(true); + if (checkOnStartup + && lastRollForFile != null + && lastRegularRoll != null + && lastRollForFile.before(lastRegularRoll)) { lastRollDate = lastRollForFile; rollover(); } @@ -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 @@ -117,7 +119,8 @@ public CronExpression getCronExpression() { * @return a ScheduledTriggeringPolicy. */ @PluginFactory - public static CronTriggeringPolicy createPolicy(@PluginConfiguration final Configuration configuration, + public static CronTriggeringPolicy createPolicy( + @PluginConfiguration final Configuration configuration, @PluginAttribute("evaluateOnStartup") final String evaluateOnStartup, @PluginAttribute("schedule") final String schedule) { CronExpression cronExpression; @@ -145,10 +148,9 @@ 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(); + // If possible, use the time rollover was supposed to occur, not the actual time. + final Date rollTime = future != null ? future.getFireTime() : new Date(); + manager.rollover(cronExpression.getPrevFireTime(rollTime), lastRollDate); 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..969e37f5812 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -27,7 +28,6 @@ import java.util.SortedMap; 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; @@ -90,10 +90,10 @@ public class DefaultRolloverStrategy extends AbstractRolloverStrategy { public static class Builder implements org.apache.logging.log4j.core.util.Builder { @PluginBuilderAttribute("max") private String max; - + @PluginBuilderAttribute("min") private String min; - + @PluginBuilderAttribute("fileIndex") private String fileIndex; @@ -126,7 +126,7 @@ public DefaultRolloverStrategy build() { useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max"); minIndex = MIN_WINDOW_SIZE; if (min != null) { - minIndex = Integer.parseInt(min); + minIndex = Integers.parseInt(min); if (minIndex < 1) { LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE); minIndex = MIN_WINDOW_SIZE; @@ -134,16 +134,29 @@ 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); + 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(), - customActions, stopCustomActionsOnError, tempCompressedFilePattern); + 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); } public String getMax() { @@ -155,8 +168,9 @@ public String getMax() { * * @param max The maximum number of files to keep. * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withMax(final String max) { + public Builder setMax(final String max) { this.max = max; return this; } @@ -170,8 +184,9 @@ public String getMin() { * * @param min The minimum number of files to keep. * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withMin(final String min) { + public Builder setMin(final String min) { this.min = min; return this; } @@ -186,8 +201,9 @@ public String getFileIndex() { * @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. * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withFileIndex(final String fileIndex) { + public Builder setFileIndex(final String fileIndex) { this.fileIndex = fileIndex; return this; } @@ -201,8 +217,9 @@ public String getCompressionLevelStr() { * * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withCompressionLevelStr(final String compressionLevelStr) { + public Builder setCompressionLevelStr(final String compressionLevelStr) { this.compressionLevelStr = compressionLevelStr; return this; } @@ -216,8 +233,9 @@ public Action[] getCustomActions() { * * @param customActions custom actions to perform asynchronously after rollover * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withCustomActions(final Action[] customActions) { + public Builder setCustomActions(final Action[] customActions) { this.customActions = customActions; return this; } @@ -231,8 +249,9 @@ public boolean isStopCustomActionsOnError() { * * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) { this.stopCustomActionsOnError = stopCustomActionsOnError; return this; } @@ -246,8 +265,9 @@ 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 + * @since 2.26.0 */ - public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) { this.tempCompressedFilePattern = tempCompressedFilePattern; return this; } @@ -258,10 +278,83 @@ public Configuration getConfig() { /** * Defines configuration. - * + * * @param config The Configuration. * @return This builder for chaining convenience + * @since 2.26.0 + */ + public Builder setConfig(final Configuration config) { + this.config = config; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setMax(String)}. + */ + @Deprecated + public Builder withMax(final String max) { + this.max = max; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setMin(String)}. + */ + @Deprecated + public Builder withMin(final String min) { + this.min = min; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFileIndex(String)}. + */ + @Deprecated + public Builder withFileIndex(final String fileIndex) { + this.fileIndex = fileIndex; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setCompressionLevelStr(String)}. */ + @Deprecated + public Builder withCompressionLevelStr(final String compressionLevelStr) { + this.compressionLevelStr = compressionLevelStr; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setCustomActions(Action[])}. + */ + @Deprecated + public Builder withCustomActions(final Action[] customActions) { + this.customActions = customActions; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setStopCustomActionsOnError(boolean)}. + */ + @Deprecated + public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + this.stopCustomActionsOnError = stopCustomActionsOnError; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setTempCompressedFilePattern(String)}. + */ + @Deprecated + public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + this.tempCompressedFilePattern = tempCompressedFilePattern; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfig(Configuration)}. + */ + @Deprecated public Builder withConfig(final Configuration config) { this.config = config; return this; @@ -300,15 +393,15 @@ public static DefaultRolloverStrategy createStrategy( final boolean stopCustomActionsOnError, @PluginConfiguration final Configuration config) { return DefaultRolloverStrategy.newBuilder() - .withMin(min) - .withMax(max) - .withFileIndex(fileIndex) - .withCompressionLevelStr(compressionLevelStr) - .withCustomActions(customActions) - .withStopCustomActionsOnError(stopCustomActionsOnError) - .withConfig(config) + .setMin(min) + .setMax(max) + .setFileIndex(fileIndex) + .setCompressionLevelStr(compressionLevelStr) + .setCustomActions(customActions) + .setStopCustomActionsOnError(stopCustomActionsOnError) + .setConfig(config) .build(); - // @formatter:on + // @formatter:on } /** @@ -320,6 +413,7 @@ public static DefaultRolloverStrategy createStrategy( * Index for most recent log file. */ private final int minIndex; + private final boolean useMax; private final int compressionLevel; private final List customActions; @@ -336,11 +430,23 @@ public static DefaultRolloverStrategy createStrategy( * @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, + 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); + this( + minIndex, + maxIndex, + useMax, + compressionLevel, + strSubstitutor, + customActions, + stopCustomActionsOnError, + null); } /** @@ -353,16 +459,22 @@ protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final * @param tempCompressedFilePatternString File pattern of the working file * used during compression, if null no temporary file are used */ - protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax, - final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions, - final boolean stopCustomActionsOnError, final String tempCompressedFilePatternString) { + protected DefaultRolloverStrategy( + final int minIndex, + final int maxIndex, + final boolean useMax, + final int compressionLevel, + final StrSubstitutor strSubstitutor, + final Action[] customActions, + final boolean stopCustomActionsOnError, + final String tempCompressedFilePatternString) { super(strSubstitutor); this.minIndex = minIndex; this.maxIndex = maxIndex; this.useMax = useMax; this.compressionLevel = compressionLevel; this.stopCustomActionsOnError = stopCustomActionsOnError; - this.customActions = customActions == null ? Collections. emptyList() : Arrays.asList(customActions); + this.customActions = customActions == null ? Collections.emptyList() : Arrays.asList(customActions); this.tempCompressedFilePattern = tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null; } @@ -408,11 +520,14 @@ private int purge(final int lowIndex, final int highIndex, final RollingFileMana * @param manager The RollingFileManager * @return true if purge was successful and rollover should be attempted. */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") private int purgeAscending(final int lowIndex, final int highIndex, final RollingFileManager manager) { 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); @@ -436,7 +551,7 @@ private int purgeAscending(final int lowIndex, final int highIndex, final Rollin String renameTo = buf.toString(); final int suffixLength = suffixLength(renameTo); if (suffixLength > 0 && suffixLength(currentName) == 0) { - renameTo = renameTo.substring(0, renameTo.length() - suffixLength); + renameTo = renameTo.substring(0, renameTo.length() - suffixLength); } final Action action = new FileRenameAction(entry.getValue().toFile(), new File(renameTo), true); try { @@ -451,8 +566,9 @@ private int purgeAscending(final int lowIndex, final int highIndex, final Rollin } } - return eligibleFiles.size() > 0 ? - (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) : lowIndex; + return eligibleFiles.size() > 0 + ? (eligibleFiles.lastKey() < highIndex ? eligibleFiles.lastKey() + 1 : highIndex) + : lowIndex; } /** @@ -464,14 +580,18 @@ private int purgeAscending(final int lowIndex, final int highIndex, final Rollin * @param manager The RollingFileManager * @return true if purge was successful and rollover should be attempted. */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") private int purgeDescending(final int lowIndex, final int highIndex, final RollingFileManager manager) { // 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) { @@ -513,11 +633,16 @@ private int purgeDescending(final int lowIndex, final int highIndex, final Rolli * @throws SecurityException if an error occurs. */ @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException { 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 +652,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(); @@ -554,14 +679,12 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec parentFile.mkdirs(); } compressAction = new CompositeAction( - Arrays.asList(fileExtension.createCompressAction(renameTo, tmpCompressedName, - true, compressionLevel), - new FileRenameAction(tmpCompressedNameFile, - renameToFile, true)), + Arrays.asList( + fileExtension.createCompressAction(renameTo, tmpCompressedName, true, compressionLevel), + new FileRenameAction(tmpCompressedNameFile, renameToFile, true)), true); } else { - compressAction = fileExtension.createCompressAction(renameTo, compressedName, - true, compressionLevel); + compressAction = fileExtension.createCompressAction(renameTo, compressedName, true, compressionLevel); } } @@ -571,24 +694,24 @@ 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()) - .build(); + .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); } - final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo), - manager.isRenameEmptyFiles()); + final FileRenameAction renameAction = + new FileRenameAction(new File(currentFileName), new File(renameTo), manager.isRenameEmptyFiles()); final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError); return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction); @@ -598,5 +721,4 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec public String toString() { return "DefaultRolloverStrategy(min=" + minIndex + ", max=" + maxIndex + ", useMax=" + useMax + ")"; } - } 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..d31a35f9fdd 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -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..7694ce48a48 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -26,7 +27,6 @@ import java.util.SortedMap; 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; @@ -60,7 +60,7 @@ public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy { private static final int DEFAULT_MAX_FILES = 7; - + /** * Builds DirectWriteRolloverStrategy instances. */ @@ -87,7 +87,7 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde public DirectWriteRolloverStrategy build() { int maxIndex = Integer.MAX_VALUE; if (maxFiles != null) { - maxIndex = Integer.parseInt(maxFiles); + maxIndex = Integers.parseInt(maxFiles); if (maxIndex < 0) { maxIndex = Integer.MAX_VALUE; } else if (maxIndex < 2) { @@ -96,8 +96,13 @@ public DirectWriteRolloverStrategy build() { } } final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION); - return new DirectWriteRolloverStrategy(maxIndex, compressionLevel, config.getStrSubstitutor(), - customActions, stopCustomActionsOnError, tempCompressedFilePattern); + return new DirectWriteRolloverStrategy( + maxIndex, + compressionLevel, + config.getStrSubstitutor(), + customActions, + stopCustomActionsOnError, + tempCompressedFilePattern); } public String getMaxFiles() { @@ -109,8 +114,9 @@ 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 + * @since 2.26.0 */ - public Builder withMaxFiles(final String maxFiles) { + public Builder setMaxFiles(final String maxFiles) { this.maxFiles = maxFiles; return this; } @@ -124,8 +130,9 @@ public String getCompressionLevelStr() { * * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withCompressionLevelStr(final String compressionLevelStr) { + public Builder setCompressionLevelStr(final String compressionLevelStr) { this.compressionLevelStr = compressionLevelStr; return this; } @@ -139,8 +146,9 @@ public Action[] getCustomActions() { * * @param customActions custom actions to perform asynchronously after rollover * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withCustomActions(final Action[] customActions) { + public Builder setCustomActions(final Action[] customActions) { this.customActions = customActions; return this; } @@ -154,8 +162,9 @@ public boolean isStopCustomActionsOnError() { * * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs * @return This builder for chaining convenience + * @since 2.26.0 */ - public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) { this.stopCustomActionsOnError = stopCustomActionsOnError; return this; } @@ -169,8 +178,9 @@ 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 + * @since 2.26.0 */ - public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) { this.tempCompressedFilePattern = tempCompressedFilePattern; return this; } @@ -181,10 +191,65 @@ public Configuration getConfig() { /** * Defines configuration. - * + * * @param config The Configuration. * @return This builder for chaining convenience + * @since 2.26.0 + */ + public Builder setConfig(final Configuration config) { + this.config = config; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setMaxFiles(String)}. + */ + @Deprecated + public Builder withMaxFiles(final String maxFiles) { + this.maxFiles = maxFiles; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setCompressionLevelStr(String)}. + */ + @Deprecated + public Builder withCompressionLevelStr(final String compressionLevelStr) { + this.compressionLevelStr = compressionLevelStr; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setCustomActions(Action[])}. + */ + @Deprecated + public Builder withCustomActions(final Action[] customActions) { + this.customActions = customActions; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setStopCustomActionsOnError(boolean)}. */ + @Deprecated + public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + this.stopCustomActionsOnError = stopCustomActionsOnError; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setTempCompressedFilePattern(String)}. + */ + @Deprecated + public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + this.tempCompressedFilePattern = tempCompressedFilePattern; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfig(Configuration)}. + */ + @Deprecated public Builder withConfig(final Configuration config) { this.config = config; return this; @@ -217,25 +282,28 @@ public static DirectWriteRolloverStrategy createStrategy( @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 + return newBuilder() + .setMaxFiles(maxFiles) + .setCompressionLevelStr(compressionLevelStr) + .setCustomActions(customActions) + .setStopCustomActionsOnError(stopCustomActionsOnError) + .setConfig(config) + .build(); + // @formatter:on } /** * Index for most recent log file. */ private final int maxFiles; + private final int compressionLevel; private final List customActions; private final boolean stopCustomActionsOnError; private volatile String currentFileName; private int nextIndex = -1; private final PatternProcessor tempCompressedFilePattern; + private volatile boolean usePrevTime = false; /** * Constructs a new instance. @@ -246,9 +314,12 @@ public static DirectWriteRolloverStrategy createStrategy( * @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) { + protected DirectWriteRolloverStrategy( + final int maxFiles, + final int compressionLevel, + final StrSubstitutor strSubstitutor, + final Action[] customActions, + final boolean stopCustomActionsOnError) { this(maxFiles, compressionLevel, strSubstitutor, customActions, stopCustomActionsOnError, null); } @@ -261,14 +332,18 @@ protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionL * @param tempCompressedFilePatternString File pattern of the working file * used during compression, if null no temporary file are used */ - protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel, - final StrSubstitutor strSubstitutor, final Action[] customActions, - final boolean stopCustomActionsOnError, final String tempCompressedFilePatternString) { + protected DirectWriteRolloverStrategy( + final int maxFiles, + final int compressionLevel, + final StrSubstitutor strSubstitutor, + final Action[] customActions, + final boolean stopCustomActionsOnError, + final String tempCompressedFilePatternString) { super(strSubstitutor); this.maxFiles = maxFiles; this.compressionLevel = compressionLevel; this.stopCustomActionsOnError = stopCustomActionsOnError; - this.customActions = customActions == null ? Collections. emptyList() : Arrays.asList(customActions); + this.customActions = customActions == null ? Collections.emptyList() : Arrays.asList(customActions); this.tempCompressedFilePattern = tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null; } @@ -313,8 +388,10 @@ 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 +400,11 @@ public String getCurrentFileName(final RollingFileManager manager) { return currentFileName; } + @Override + public void clearCurrentFileName() { + currentFileName = null; + } + /** * Performs the rollover. * @@ -331,6 +413,9 @@ public String getCurrentFileName(final RollingFileManager manager) { * @throws SecurityException if an error occurs. */ @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException { LOGGER.debug("Rolling " + currentFileName); if (maxFiles < 0) { @@ -349,7 +434,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); @@ -360,30 +445,29 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec parentFile.mkdirs(); } compressAction = new CompositeAction( - Arrays.asList(fileExtension.createCompressAction(sourceName, tmpCompressedName, - true, compressionLevel), - new FileRenameAction(tmpCompressedNameFile, - new File(compressedName), true)), + Arrays.asList( + fileExtension.createCompressAction( + sourceName, tmpCompressedName, true, compressionLevel), + new FileRenameAction(tmpCompressedNameFile, new File(compressedName), true)), true); } else { - compressAction = fileExtension.createCompressAction(sourceName, compressedName, - true, compressionLevel); + compressAction = fileExtension.createCompressAction(sourceName, compressedName, true, compressionLevel); } } 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()) - .build(); + .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); } @@ -396,5 +480,4 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec public String toString() { return "DirectWriteRolloverStrategy(maxFiles=" + maxFiles + ')'; } - } 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..a7622b2c5f8 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 @@ -1,24 +1,24 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.util.Objects; - import org.apache.logging.log4j.core.appender.rolling.action.Action; import org.apache.logging.log4j.core.appender.rolling.action.CommonsCompressAction; import org.apache.logging.log4j.core.appender.rolling.action.GzCompressAction; @@ -30,49 +30,78 @@ public enum FileExtension { ZIP(".zip") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { return new ZipCompressAction(source(renameTo), target(compressedName), deleteSource, compressionLevel); } }, GZ(".gz") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { - return new GzCompressAction(source(renameTo), target(compressedName), deleteSource); + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + return new GzCompressAction(source(renameTo), target(compressedName), deleteSource, compressionLevel); } }, BZIP2(".bz2") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { - // One of "gz", "bzip2", "xz", "pack200", or "deflate". + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + // One of "gz", "bzip2", "xz", "zst", "pack200", or "deflate". return new CommonsCompressAction("bzip2", source(renameTo), target(compressedName), deleteSource); } }, DEFLATE(".deflate") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { - // One of "gz", "bzip2", "xz", "pack200", or "deflate". + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + // One of "gz", "bzip2", "xz", "zst", "pack200", or "deflate". return new CommonsCompressAction("deflate", source(renameTo), target(compressedName), deleteSource); } }, PACK200(".pack200") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { - // One of "gz", "bzip2", "xz", "pack200", or "deflate". + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + // One of "gz", "bzip2", "xz", "zst", "pack200", or "deflate". return new CommonsCompressAction("pack200", source(renameTo), target(compressedName), deleteSource); } }, XZ(".xz") { @Override - Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, - final int compressionLevel) { - // One of "gz", "bzip2", "xz", "pack200", or "deflate". + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + // One of "gz", "bzip2", "xz", "zstd", "pack200", or "deflate". return new CommonsCompressAction("xz", source(renameTo), target(compressedName), deleteSource); } + }, + ZSTD(".zst") { + @Override + public Action createCompressAction( + final String renameTo, + final String compressedName, + final boolean deleteSource, + final int compressionLevel) { + // One of "gz", "bzip2", "xz", "zstd", "pack200", or "deflate". + return new CommonsCompressAction("zstd", source(renameTo), target(compressedName), deleteSource); + } }; public static FileExtension lookup(final String fileExtension) { @@ -95,15 +124,15 @@ 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; } - abstract Action createCompressAction(String renameTo, String compressedName, boolean deleteSource, - int compressionLevel); + public abstract Action createCompressAction( + String renameTo, String compressedName, boolean deleteSource, int compressionLevel); - String getExtension() { + public String getExtension() { return extension; } @@ -115,11 +144,17 @@ int length() { return extension.length(); } + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") File source(final String fileName) { return new File(fileName); } + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") 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..94a407b7bf6 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.rolling; import java.text.NumberFormat; @@ -22,7 +21,6 @@ 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; @@ -30,20 +28,21 @@ * 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 +59,40 @@ 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); + // Read the quantity. + final String quantityString = matcher.group(1); + final double quantity = NumberFormat.getNumberInstance(Locale.ROOT) + .parse(quantityString) + .doubleValue(); - 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 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..fa994c3043c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/NoOpTriggeringPolicy.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.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.PluginFactory; + +/* + * Never triggers and is handy for edge-cases in tests for example. + * + * @since 2.11.1 + */ +@Plugin(name = "NoOpTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) +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..047aa1f10e1 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 @@ -1,23 +1,22 @@ /* * 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.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; @@ -62,8 +61,11 @@ private static long initStartTime() { return result; } catch (final Throwable t) { - StatusLogger.getLogger().error("Unable to call ManagementFactory.getRuntimeMXBean().getStartTime(), " - + "using system time for OnStartupTriggeringPolicy", 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(); } @@ -76,6 +78,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); } 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..ea8998f0175 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -21,10 +21,8 @@ import java.util.Calendar; import java.util.Date; import java.util.List; - +import java.util.TimeZone; 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; @@ -52,12 +50,16 @@ 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 TimeZone timeZone; private final String pattern; @@ -80,15 +82,16 @@ public PatternProcessor(final String pattern) { 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) { if (converter instanceof DatePatternConverter) { final DatePatternConverter dateConverter = (DatePatternConverter) converter; frequency = calculateFrequency(dateConverter.getPattern()); + timeZone = dateConverter.getTimeZone(); } } } @@ -106,6 +109,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 +138,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. @@ -141,9 +160,9 @@ public long getNextTime(final long currentMillis, final int increment, final boo if (frequency == null) { throw new IllegalStateException("Pattern does not contain a date"); } - final Calendar currentCal = Calendar.getInstance(); + final Calendar currentCal = Calendar.getInstance(timeZone); currentCal.setTimeInMillis(currentMillis); - final Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(timeZone); currentCal.setMinimalDaysInFirstWeek(7); cal.setMinimalDaysInFirstWeek(7); cal.set(currentCal.get(Calendar.YEAR), 0, 1, 0, 0, 0); @@ -213,13 +232,21 @@ 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) { if (LOGGER.isTraceEnabled()) { - LOGGER.trace("PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", // - format(nextTime), format(nextFileTime), format(prevFileTime), format(System.currentTimeMillis()), frequency); + LOGGER.trace( + "PatternProcessor.getNextTime returning {}, nextFileTime={}, prevFileTime={}, current={}, freq={}", // + format(nextTime), + format(nextFileTime), + format(prevFileTime), + format(System.currentTimeMillis()), + frequency); } return nextTime; } @@ -229,7 +256,7 @@ private String format(final long time) { } private void increment(final Calendar cal, final int type, final int increment, final boolean modulate) { - final int interval = modulate ? increment - (cal.get(type) % increment) : increment; + final int interval = modulate ? increment - (cal.get(type) % increment) : increment; cal.add(type, interval); } @@ -262,15 +289,21 @@ public final void formatFileName(final StrSubstitutor subst, final StringBuilder * @param buf string buffer to which formatted file name is appended, may not be null. * @param obj object to be evaluated in formatting, may not be null. */ - public final void formatFileName(final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime, - final Object obj) { + public final void formatFileName( + final StrSubstitutor subst, final StringBuilder buf, final boolean useCurrentTime, 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 : - prevFileTime != 0 ? prevFileTime : System.currentTimeMillis(); + LOGGER.debug( + "Formatting file name. useCurrentTime={}. currentFileTime={}, prevFileTime={}", + useCurrentTime, + currentFileTime, + prevFileTime); + 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(); - final String fileName = subst.replace(event, buf); + // LOG4J2-3643 + final String fileName = subst.replace(null, buf); buf.setLength(0); buf.append(fileName); } @@ -292,6 +325,10 @@ protected final void formatFileName(final StringBuilder buf, final Object... obj } private RolloverFrequency calculateFrequency(final String pattern) { + // The UNIX and UNIX_MILLIS converters do not have a pattern + if (pattern == null) { + return null; + } if (patternContains(pattern, MILLIS_CHAR)) { return RolloverFrequency.EVERY_MILLISECOND; } @@ -344,5 +381,4 @@ public RolloverFrequency getFrequency() { public long getNextFileTime() { return nextFileTime; } - } 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..b033f5785e0 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 @@ -1,35 +1,41 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; 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.Date; 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; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LifeCycle; import org.apache.logging.log4j.core.LifeCycle2; @@ -53,6 +59,7 @@ public class RollingFileManager extends FileManager { private static 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,96 +68,196 @@ 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. */ - private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS, - new EmptyQueue(), threadFactory); + us to make sure all the Threads are completed when the Manager is stopped. */ + private final ExecutorService asyncExecutor = + new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS, new EmptyQueue(), threadFactory); private static final AtomicReferenceFieldUpdater triggeringPolicyUpdater = - AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy"); + AtomicReferenceFieldUpdater.newUpdater( + RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy"); private static final AtomicReferenceFieldUpdater rolloverStrategyUpdater = - AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy"); + AtomicReferenceFieldUpdater.newUpdater( + RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy"); private static final AtomicReferenceFieldUpdater patternProcessorUpdater = - AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor"); + 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])); + protected RollingFileManager( + final String fileName, + final String pattern, + final OutputStream os, + final boolean append, + final long size, + final long initialTime, + final TriggeringPolicy triggeringPolicy, + final RolloverStrategy rolloverStrategy, + final String advertiseURI, + final Layout layout, + final int bufferSize, + final boolean writeHeader) { + this( + fileName, + pattern, + os, + append, + size, + initialTime, + 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); + protected RollingFileManager( + final String fileName, + final String pattern, + final OutputStream os, + final boolean append, + final long size, + final long initialTime, + final TriggeringPolicy triggeringPolicy, + final RolloverStrategy rolloverStrategy, + final String advertiseURI, + final Layout layout, + final boolean writeHeader, + final ByteBuffer buffer) { + super(fileName != null ? fileName : pattern, os, append, false, advertiseURI, layout, 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 DirectWriteRolloverStrategy; } @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); + 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 initialTime, + final TriggeringPolicy triggeringPolicy, + final RolloverStrategy rolloverStrategy, + final String advertiseURI, + final Layout layout, + final boolean writeHeader, + final ByteBuffer buffer) { + super( + loggerContext, + fileName != null ? fileName : pattern, + os, + append, + false, + createOnDemand, + advertiseURI, + layout, + 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 DirectWriteRolloverStrategy; } /** * @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 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); + 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 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 != null ? fileName : pattern, + os, + append, + false, + createOnDemand, + advertiseURI, + layout, + filePermissions, + fileOwner, + fileGroup, + writeHeader, + buffer); this.size = size; - this.initialTime = time; + this.initialTime = initialTime; + this.patternProcessor = new PatternProcessor(pattern); + this.patternProcessor.setPrevFileTime(initialTime); this.triggeringPolicy = triggeringPolicy; this.rolloverStrategy = rolloverStrategy; - this.patternProcessor = new PatternProcessor(pattern); - this.patternProcessor.setPrevFileTime(time); this.fileName = fileName; - this.fileExtension = FileExtension.lookupForFile(pattern); + this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy; } + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") 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(); } + 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(); + } + } } } @@ -173,11 +280,21 @@ public void initialize() { * @param configuration The configuration. * @return A RollingFileManager. */ - public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append, - final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy, - final String advertiseURI, final Layout layout, final int bufferSize, - final boolean immediateFlush, final boolean createOnDemand, - final String filePermissions, final String fileOwner, final String fileGroup, + public static RollingFileManager getFileManager( + final String fileName, + final String pattern, + final boolean append, + final boolean bufferedIO, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final String advertiseURI, + final Layout layout, + final int bufferSize, + final boolean immediateFlush, + final boolean createOnDemand, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) { @@ -185,9 +302,43 @@ public static RollingFileManager getFileManager(final String fileName, final Str return null; } final String name = fileName == null ? pattern : fileName; - return narrow(RollingFileManager.class, getManager(name, new FactoryData(fileName, pattern, append, - bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand, - filePermissions, fileOwner, fileGroup, configuration), factory)); + return narrow( + RollingFileManager.class, + getManager( + name, + new FactoryData( + fileName, + pattern, + append, + bufferedIO, + policy, + strategy, + advertiseURI, + layout, + bufferSize, + immediateFlush, + createOnDemand, + 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); } /** @@ -196,20 +347,35 @@ public static RollingFileManager getFileManager(final String fileName, final Str */ @Override public String getFileName() { - if (rolloverStrategy instanceof DirectFileRolloverStrategy) { + if (directWrite) { fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this); } return fileName; } + @Override + protected void createParentDir(File file) { + if (directWrite) { + final File parent = file.getParentFile(); + // If the parent is null the file is in the current working directory. + if (parent != null) { + parent.mkdirs(); + } + } + } + + public boolean isDirectWrite() { + return directWrite; + } + public FileExtension getFileExtension() { - return fileExtension; + return patternProcessor.getFileExtension(); } // override to make visible for unit tests @Override - protected synchronized void write(final byte[] bytes, final int offset, final int length, - final boolean immediateFlush) { + protected synchronized void write( + final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { super.write(bytes, offset, length, immediateFlush); } @@ -255,7 +421,7 @@ 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); @@ -282,7 +448,8 @@ public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { if (asyncExecutor.isTerminated()) { LOGGER.debug("All asynchronous threads have terminated"); } else { - LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed"); + LOGGER.debug( + "RollingFileManager shutting down but some asynchronous services may not have completed"); } } catch (final InterruptedException inner) { LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed."); @@ -305,22 +472,68 @@ public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { return status; } + public synchronized void rollover(final Date prevFileTime, final Date prevRollTime) { + LOGGER.debug("Rollover PrevFileTime: {}, PrevRollTime: {}", prevFileTime.getTime(), prevRollTime.getTime()); + getPatternProcessor().setPrevFileTime(prevFileTime.getTime()); + getPatternProcessor().setCurrentFileTime(prevRollTime.getTime()); + 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 (RolloverListener listener : rolloverListeners) { + try { + listener.rolloverTriggered(currentFileName); + } catch (Exception ex) { + LOGGER.warn( + "Rollover Listener {} failed with {}: {}", + listener.getClass().getSimpleName(), + ex.getClass().getName(), + ex.getMessage()); + } + } + } + + final 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 (RolloverListener listener : rolloverListeners) { + try { + listener.rolloverComplete(currentFileName); + } catch (Exception ex) { + LOGGER.warn( + "Rollover Listener {} failed with {}: {}", + listener.getClass().getSimpleName(), + ex.getClass().getName(), + ex.getMessage()); + } } } } - protected void createFileAfterRollover() throws IOException { + protected void createFileAfterRollover() throws IOException { setOutputStream(createOutputStream()); } @@ -348,10 +561,8 @@ public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) { if (policy instanceof LifeCycle) { ((LifeCycle) policy).stop(); } - } else { - if (triggeringPolicy instanceof LifeCycle) { - ((LifeCycle) triggeringPolicy).stop(); - } + } else if (triggeringPolicy instanceof LifeCycle) { + ((LifeCycle) triggeringPolicy).stop(); } } @@ -374,6 +585,15 @@ public T getTriggeringPolicy() { return (T) this.triggeringPolicy; } + /** + * Package-private access for tests only. + * + * @return The semaphore that controls access to the rollover operation. + */ + Semaphore getSemaphore() { + return semaphore; + } + /** * Returns the rollover strategy. * @return The RolloverStrategy @@ -384,47 +604,46 @@ public RolloverStrategy getRolloverStrategy() { private boolean rollover(final RolloverStrategy strategy) { - boolean releaseRequired = false; + boolean outputStreamClosed = false; try { // Block until the asynchronous operation is completed. semaphore.acquire(); - releaseRequired = true; } catch (final InterruptedException e) { logError("Thread interrupted while attempting to check rollover", e); - return false; + return outputStreamClosed; } - boolean success = true; + boolean asyncActionStarted = true; try { final RolloverDescription descriptor = strategy.rollover(this); if (descriptor != null) { writeFooter(); closeOutputStream(); + outputStreamClosed = true; + boolean syncActionSuccess = true; if (descriptor.getSynchronous() != null) { LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous()); try { - success = descriptor.getSynchronous().execute(); + syncActionSuccess = descriptor.getSynchronous().execute(); } catch (final Exception ex) { - success = false; + syncActionSuccess = false; logError("Caught error in synchronous task", ex); } } - if (success && descriptor.getAsynchronous() != null) { + if (syncActionSuccess && descriptor.getAsynchronous() != null) { LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous()); asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this)); - releaseRequired = false; + asyncActionStarted = false; } - return true; } - return false; + return outputStreamClosed; } finally { - if (releaseRequired) { + if (asyncActionStarted) { semaphore.release(); } } - } /** @@ -531,10 +750,21 @@ private static class FactoryData extends ConfigurationFactoryData { * @param fileGroup File group * @param configuration The configuration */ - public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO, - final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, - final Layout layout, final int bufferSize, final boolean immediateFlush, - final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup, + public FactoryData( + final String fileName, + final String pattern, + final boolean append, + final boolean bufferedIO, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final String advertiseURI, + final Layout layout, + final int bufferSize, + final boolean immediateFlush, + final boolean createOnDemand, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { super(configuration); this.fileName = fileName; @@ -594,12 +824,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()); } /** @@ -614,14 +850,14 @@ private static class RollingFileManagerFactory implements ManagerFactory 0) { + LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath()); + 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 { /** @@ -685,7 +957,8 @@ public void put(final Runnable runnable) throws InterruptedException { } @Override - public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException { + public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) + throws InterruptedException { Thread.sleep(timeUnit.toMillis(timeout)); return false; } @@ -697,7 +970,5 @@ public boolean addAll(final Collection collection) { } return false; } - } - } 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..047e397dd14 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 @@ -1,28 +1,29 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; import java.io.OutputStream; 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; import org.apache.logging.log4j.core.appender.AppenderLoggingException; @@ -45,33 +46,86 @@ 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); + 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 TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, + 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 initialTime, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final String advertiseURI, final Layout layout, - final String filePermissions, final String fileOwner, final String fileGroup, + 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, - filePermissions, fileOwner, fileGroup, - writeHeader, ByteBuffer.wrap(new byte[bufferSize])); + super( + loggerContext, + fileName, + pattern, + os, + append, + false, + size, + initialTime, + policy, + strategy, + advertiseURI, + layout, + filePermissions, + fileOwner, + fileGroup, + writeHeader, + ByteBuffer.wrap(new byte[bufferSize])); this.randomAccessFile = raf; - isEndOfBatch.set(Boolean.FALSE); writeHeader(); } @@ -87,7 +141,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); } @@ -96,33 +150,68 @@ private void writeHeader() { } } - public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, - final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, - final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, - final Layout layout, final String filePermissions, final String fileOwner, final String fileGroup, + public static RollingRandomAccessFileManager getRollingRandomAccessFileManager( + final String fileName, + final String filePattern, + final boolean isAppend, + final boolean immediateFlush, + final int bufferSize, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final String advertiseURI, + final Layout layout, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) { LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy"); return null; } final String name = fileName == null ? filePattern : fileName; - return narrow(RollingRandomAccessFileManager.class, getManager(name, new FactoryData(fileName, filePattern, isAppend, - immediateFlush, bufferSize, policy, strategy, advertiseURI, layout, - filePermissions, fileOwner, fileGroup, configuration), FACTORY)); + return narrow( + RollingRandomAccessFileManager.class, + getManager( + name, + new FactoryData( + fileName, + filePattern, + isAppend, + immediateFlush, + bufferSize, + policy, + strategy, + advertiseURI, + layout, + 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 @Override - protected synchronized void write(final byte[] bytes, final int offset, final int length, - final boolean immediateFlush) { + protected synchronized void write( + final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { super.write(bytes, offset, length, immediateFlush); } @@ -130,10 +219,7 @@ 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); - FileUtils.makeParentDirs(file); - createFileAfterRollover(fileName); + createFileAfterRollover(); } randomAccessFile.write(bytes, offset, length); size += length; @@ -144,12 +230,24 @@ protected synchronized void writeToDestination(final byte[] bytes, final int off } @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") protected void createFileAfterRollover() throws IOException { - createFileAfterRollover(getFileName()); + final String fileName = getFileName(); + final File file = new File(fileName); + FileUtils.makeParentDirs(file); + createFileAfterRollover(fileName); } - private void createFileAfterRollover(String fileName) throws IOException { + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") + 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 +262,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; } /** @@ -186,8 +287,8 @@ public int getBufferSize() { /** * Factory to create a RollingRandomAccessFileManager. */ - private static class RollingRandomAccessFileManagerFactory implements - ManagerFactory { + private static class RollingRandomAccessFileManagerFactory + implements ManagerFactory { /** * Create the RollingRandomAccessFileManager. @@ -197,6 +298,9 @@ private static class RollingRandomAccessFileManagerFactory implements * @return a RollingFileManager. */ @Override + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { File file = null; long size = 0; @@ -237,9 +341,25 @@ 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, - data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader); + final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager( + data.getLoggerContext(), + raf, + name, + data.pattern, + NullOutputStream.getInstance(), + data.append, + data.immediateFlush, + data.bufferSize, + size, + time, + data.policy, + data.strategy, + data.advertiseURI, + data.layout, + data.filePermissions, + data.fileOwner, + data.fileGroup, + writeHeader); if (rrm.isAttributeViewEnabled()) { rrm.defineAttributeView(file.toPath()); } @@ -281,10 +401,19 @@ private static class FactoryData extends ConfigurationFactoryData { * @param fileGroup File group * @param configuration */ - public FactoryData(final String fileName, final String pattern, final boolean append, final boolean immediateFlush, - final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, - final String advertiseURI, final Layout layout, - final String filePermissions, final String fileOwner, final String fileGroup, + public FactoryData( + final String fileName, + final String pattern, + final boolean append, + final boolean immediateFlush, + final int bufferSize, + final TriggeringPolicy policy, + final RolloverStrategy strategy, + final String advertiseURI, + final Layout layout, + final String filePermissions, + final String fileOwner, + final String fileGroup, final Configuration configuration) { super(configuration); this.fileName = fileName; @@ -301,21 +430,30 @@ 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/RolloverDescription.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescription.java index 10c2df9a052..2aca4ba41f1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescription.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescription.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescriptionImpl.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescriptionImpl.java index 8b1c848ff61..e7f381d600b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescriptionImpl.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverDescriptionImpl.java @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling; import java.util.Objects; - import org.apache.logging.log4j.core.appender.rolling.action.Action; /** @@ -55,8 +54,8 @@ public final class RolloverDescriptionImpl implements RolloverDescription { * @param asynchronous action to be completed after close of current active log file and * before next rollover attempt. */ - public RolloverDescriptionImpl(final String activeFileName, final boolean append, final Action synchronous, - final Action asynchronous) { + public RolloverDescriptionImpl( + final String activeFileName, final boolean append, final Action synchronous, final Action asynchronous) { Objects.requireNonNull(activeFileName, "activeFileName"); this.append = append; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverFrequency.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverFrequency.java index 318f3ebaf98..d18fa747d7a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverFrequency.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverFrequency.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; 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..953c521b011 --- /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/RolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverStrategy.java index 10e8a5a4992..5d796e21b8b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverStrategy.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -28,6 +28,6 @@ public interface RolloverStrategy { * @param manager The RollingFileManager name for current active log file. * @return Description of pending rollover, may be null to indicate no rollover at this time. * @throws SecurityException if denied access to log files. - */ + */ RolloverDescription rollover(final RollingFileManager manager) throws SecurityException; } 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..9f1fed8b882 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -66,7 +66,6 @@ public void initialize(final RollingFileManager aManager) { this.manager = aManager; } - /** * Returns true if a rollover should occur. * @param event A reference to the currently event. @@ -97,5 +96,4 @@ public static SizeBasedTriggeringPolicy createPolicy(@PluginAttribute("size") fi 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..54f406ec8c3 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 @@ -1,24 +1,24 @@ /* * 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.logging.log4j.core.appender.rolling; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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; @@ -33,18 +33,17 @@ @Plugin(name = "TimeBasedTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) public final class TimeBasedTriggeringPolicy extends AbstractTriggeringPolicy { - public static class Builder implements org.apache.logging.log4j.core.util.Builder { @PluginBuilderAttribute private int interval = 1; - + @PluginBuilderAttribute private boolean modulate = false; - + @PluginBuilderAttribute private int maxRandomDelay = 0; - + @Override public TimeBasedTriggeringPolicy build() { final long maxRandomDelayMillis = TimeUnit.SECONDS.toMillis(maxRandomDelay); @@ -62,22 +61,57 @@ public boolean isModulate() { public int getMaxRandomDelay() { return maxRandomDelay; } - - public Builder withInterval(final int interval){ + + /** + * @since 2.26.0 + */ + public Builder setInterval(final int interval) { this.interval = interval; return this; } - - public Builder withModulate(final boolean modulate){ + + /** + * @since 2.26.0 + */ + public Builder setModulate(final boolean modulate) { this.modulate = modulate; return this; } - - public Builder withMaxRandomDelay(final int maxRandomDelay){ + + /** + * @since 2.26.0 + */ + public Builder setMaxRandomDelay(final int maxRandomDelay) { this.maxRandomDelay = maxRandomDelay; return this; } + /** + * @deprecated since 2.26.0 use {@link #setInterval(int)}. + */ + @Deprecated + public Builder withInterval(final int interval) { + this.interval = interval; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setModulate(boolean)}. + */ + @Deprecated + public Builder withModulate(final boolean modulate) { + this.modulate = modulate; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setMaxRandomDelay(int)}. + */ + @Deprecated + public Builder withMaxRandomDelay(final int maxRandomDelay) { + this.maxRandomDelay = maxRandomDelay; + return this; + } } private long nextRolloverMillis; @@ -106,14 +140,20 @@ public long getNextRolloverMillis() { * @param aManager The RollingFileManager. */ @Override + @SuppressFBWarnings("PREDICTABLE_RANDOM") public void initialize(final RollingFileManager aManager) { this.manager = aManager; - + long current = aManager.getFileTime(); + if (current == 0) { + current = System.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); } /** @@ -122,14 +162,13 @@ public void initialize(final RollingFileManager aManager) { * @return true if a rollover should occur. */ @Override + @SuppressFBWarnings("PREDICTABLE_RANDOM") 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(System.currentTimeMillis()); return true; } return false; @@ -144,14 +183,13 @@ public boolean isTriggeringEvent(final LogEvent event) { */ @Deprecated public static TimeBasedTriggeringPolicy createPolicy( - @PluginAttribute("interval") final String interval, - @PluginAttribute("modulate") final String modulate) { + @PluginAttribute("interval") final String interval, @PluginAttribute("modulate") final String modulate) { return newBuilder() - .withInterval(Integers.parseInt(interval, 1)) - .withModulate(Boolean.parseBoolean(modulate)) + .setInterval(Integers.parseInt(interval, 1)) + .setModulate(Boolean.parseBoolean(modulate)) .build(); } - + @PluginBuilderFactory public static TimeBasedTriggeringPolicy.Builder newBuilder() { return new Builder(); @@ -162,5 +200,4 @@ public String toString() { return "TimeBasedTriggeringPolicy(nextRolloverMillis=" + nextRolloverMillis + ", interval=" + interval + ", modulate=" + modulate + ")"; } - } 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..f4f7608544f 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling; @@ -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 */ { @@ -31,7 +31,6 @@ public interface TriggeringPolicy /* TODO 3.0: extends LifeCycle */ { * Initializes this triggering policy. * @param manager The RollingFileManager. */ - void initialize(final RollingFileManager manager); /** 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..2ca052835ae 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 @@ -1,32 +1,30 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.io.IOException; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; - /** * Abstract base class for implementations of Action. */ public abstract class AbstractAction implements Action { - + /** * Allows subclasses access to the status logger without creating another instance. */ @@ -44,8 +42,7 @@ public abstract class AbstractAction implements Action { /** * Constructor. */ - protected AbstractAction() { - } + protected AbstractAction() {} /** * Performs action. @@ -64,8 +61,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 +102,6 @@ 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..c8d251f672f 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 @@ -1,22 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling.action; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitor; @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.lookup.StrSubstitutor; /** @@ -45,7 +44,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 @@ -54,11 +53,16 @@ public abstract class AbstractPathAction extends AbstractAction { * @param pathFilters an array of path filters (if more than one, they all need to accept a path before it is * processed). */ - protected AbstractPathAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, - final PathCondition[] pathFilters, final StrSubstitutor subst) { + 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) - : Collections. emptySet(); + this.options = followSymbolicLinks + ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) + : Collections.emptySet(); this.maxDepth = maxDepth; this.pathConditions = Arrays.asList(Arrays.copyOf(pathFilters, pathFilters.length)); this.subst = subst; @@ -87,28 +91,31 @@ 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} */ - protected abstract FileVisitor createFileVisitor(final Path visitorBaseDir, - final List conditions); + protected abstract FileVisitor createFileVisitor( + final Path visitorBaseDir, final List conditions); /** * 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) */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") public Path getBasePath() { return Paths.get(subst.replace(getBasePathString())); } /** * 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 +128,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 +145,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 +155,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/Action.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Action.java index 2e1cead0318..499f6d0ae05 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Action.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Action.java @@ -1,24 +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. */ package org.apache.logging.log4j.core.appender.rolling.action; import java.io.IOException; - /** * The Action interface should be implemented by any class that performs * file system actions for RollingFileAppenders after the close of 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..16889695782 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -23,7 +23,6 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Objects; - import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.compress.compressors.CompressorStreamFactory; import org.apache.commons.compress.utils.IOUtils; @@ -36,7 +35,7 @@ public final class CommonsCompressAction extends AbstractAction { private static final int BUF_SIZE = 8192; /** - * Compressor name. One of "gz", "bzip2", "xz", "pack200" or "deflate". + * Compressor name. One of "gz", "bzip2", "xz", "zst", "pack200" or "deflate". */ private final String name; @@ -58,14 +57,14 @@ public final class CommonsCompressAction extends AbstractAction { /** * Creates new instance of Bzip2CompressAction. * - * @param name the compressor name. One of "gz", "bzip2", "xz", "pack200", or "deflate". + * @param name the compressor name. One of "gz", "bzip2", "xz", "zst", "pack200", or "deflate". * @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. */ - public CommonsCompressAction(final String name, final File source, final File destination, - final boolean deleteSource) { + public CommonsCompressAction( + final String name, final File source, final File destination, final boolean deleteSource) { Objects.requireNonNull(source, "source"); Objects.requireNonNull(destination, "destination"); this.name = name; @@ -88,7 +87,7 @@ public boolean execute() throws IOException { /** * Compresses a file. * - * @param name the compressor name, i.e. "gz", "bzip2", "xz", "pack200", or "deflate". + * @param name the compressor name, i.e. "gz", "bzip2", "xz", "zstd", "pack200", or "deflate". * @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 @@ -97,18 +96,19 @@ public boolean execute() throws IOException { * @return true if source file compressed. * @throws IOException on IO exception. */ - public static boolean execute(final String name, final File source, final File destination, - final boolean deleteSource) throws IOException { + public static boolean execute( + final String name, final File source, final File destination, final boolean deleteSource) + throws IOException { if (!source.exists()) { return false; } - LOGGER.debug("Starting {} compression of {}", name, source.getPath() ); + LOGGER.debug("Starting {} compression of {}", name, source.getPath()); try (final FileInputStream input = new FileInputStream(source); + final FileOutputStream fileOutput = new FileOutputStream(destination); final BufferedOutputStream output = new BufferedOutputStream( - new CompressorStreamFactory().createCompressorOutputStream(name, new FileOutputStream( - destination)))) { + new CompressorStreamFactory().createCompressorOutputStream(name, fileOutput))) { IOUtils.copy(input, output, BUF_SIZE); - LOGGER.debug("Finished {} compression of {}", name, source.getPath() ); + LOGGER.debug("Finished {} compression of {}", name, source.getPath()); } catch (final CompressorException e) { throw new IOException(e); } @@ -118,7 +118,8 @@ public static boolean execute(final String name, final File source, final File d if (Files.deleteIfExists(source.toPath())) { LOGGER.debug("Deleted {}", source.toString()); } else { - LOGGER.warn("Unable to delete {} after {} compression. File did not exist", source.toString(), name); + LOGGER.warn( + "Unable to delete {} after {} compression. File did not exist", source.toString(), name); } } catch (final Exception ex) { LOGGER.warn("Unable to delete {} after {} compression, {}", source.toString(), name, ex.getMessage()); @@ -140,8 +141,8 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return CommonsCompressAction.class.getSimpleName() + '[' + source + " to " + destination - + ", deleteSource=" + deleteSource + ']'; + return CommonsCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", deleteSource=" + + deleteSource + ']'; } public String getName() { 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..e240d1b2133 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -20,12 +20,11 @@ import java.util.Arrays; import java.util.List; - /** * A group of Actions to be executed in sequence. */ public class CompositeAction extends AbstractAction { - + /** * Actions to perform. */ @@ -42,8 +41,7 @@ public class CompositeAction extends AbstractAction { * @param actions list of actions, may not be null. * @param stopOnError if true, stop on the first false return value or exception. */ - public CompositeAction(final List actions, - final boolean stopOnError) { + public CompositeAction(final List actions, final boolean stopOnError) { this.actions = new Action[actions.size()]; actions.toArray(this.actions); this.stopOnError = stopOnError; @@ -99,7 +97,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..13d40b46815 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.io.IOException; @@ -23,7 +22,6 @@ import java.nio.file.Path; 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; @@ -45,22 +43,28 @@ public class DeleteAction extends AbstractPathAction { /** * 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 * means that only the starting file is visited, unless denied by the security manager. A value of * MAX_VALUE may be used to indicate that all levels should be visited. * @param testMode if true, files are not deleted but instead a message is printed to the status logger + * href="https://logging.apache.org/log4j/2.x/manual/status-logger.html">status logger * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. * @param sorter sorts * @param pathConditions an array of path filters (if more than one, they all need to accept a path before it is * deleted). * @param scriptCondition */ - DeleteAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, final boolean testMode, - final PathSorter sorter, final PathCondition[] pathConditions, final ScriptCondition scriptCondition, + DeleteAction( + final String basePath, + final boolean followSymbolicLinks, + final int maxDepth, + final boolean testMode, + final PathSorter sorter, + final PathCondition[] pathConditions, + final ScriptCondition scriptCondition, final StrSubstitutor subst) { super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst); this.testMode = testMode; @@ -74,7 +78,7 @@ public class DeleteAction extends AbstractPathAction { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute() */ @Override @@ -113,7 +117,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 +128,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,7 +157,7 @@ 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 */ @@ -166,7 +170,7 @@ List getSortedPaths() throws IOException { /** * 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,18 +184,18 @@ 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 * means that only the starting file is visited, unless denied by the security manager. A value of * MAX_VALUE may be used to indicate that all levels should be visited. - * @param testMode if true, files are not deleted but instead a message is printed to the status logger - * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. - * Default is false. - * @param PathSorter a plugin implementing the {@link PathSorter} interface - * @param PathConditions an array of path conditions (if more than one, they all need to accept a path before it is + * @param testMode if true, files are not deleted but instead a message is printed to the + * status logger + * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. + * Default is false. + * @param sorterParameter a plugin implementing the {@link PathSorter} interface + * @param pathConditions an array of path conditions (if more than one, they all need to accept a path before it is * deleted). * @param config The Configuration. * @return A DeleteAction. @@ -199,7 +203,7 @@ protected FileVisitor createFileVisitor(final Path visitorBaseDir, final L @PluginFactory public static DeleteAction createDeleteAction( // @formatter:off - @PluginAttribute("basePath") final String basePath, + @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, @@ -207,9 +211,16 @@ public static DeleteAction createDeleteAction( @PluginElement("PathConditions") final PathCondition[] pathConditions, @PluginElement("ScriptCondition") final ScriptCondition scriptCondition, @PluginConfiguration final Configuration config) { - // @formatter:on + // @formatter:on final PathSorter sorter = sorterParameter == null ? new PathSortByModificationTime(true) : sorterParameter; - return new DeleteAction(basePath, followLinks, maxDepth, testMode, sorter, pathConditions, scriptCondition, + return new DeleteAction( + basePath, + followLinks, + maxDepth, + testMode, + sorter, + pathConditions, + scriptCondition, config.getStrSubstitutor()); } } 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..f40f4df68a6 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 @@ -1,97 +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.appender.rolling.action; - -import java.io.IOException; -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; -import java.util.List; -import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored. - */ -public class DeletingVisitor extends SimpleFileVisitor { - private static final Logger LOGGER = StatusLogger.getLogger(); - - private final Path basePath; - private final boolean testMode; - private final List pathConditions; - - /** - * 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 status logger - * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. - */ - public DeletingVisitor(final Path basePath, final List pathConditions, - final boolean testMode) { - this.testMode = testMode; - this.basePath = Objects.requireNonNull(basePath, "basePath"); - this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions"); - for (final PathCondition condition : pathConditions) { - condition.beforeFileTreeWalk(); - } - } - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - for (final PathCondition pathFilter : pathConditions) { - final Path relative = basePath.relativize(file); - if (!pathFilter.accept(basePath, relative, attrs)) { - LOGGER.trace("Not deleting base={}, relative={}", basePath, relative); - return FileVisitResult.CONTINUE; - } - } - if (isTestMode()) { - LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file); - } else { - delete(file); - } - return FileVisitResult.CONTINUE; - } - - /** - * Deletes the specified file. - * - * @param file the file to delete - * @throws IOException if a problem occurred deleting the file - */ - protected void delete(final Path file) throws IOException { - LOGGER.trace("Deleting {}", file); - Files.deleteIfExists(file); - } - - /** - * 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() { - return testMode; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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; +import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * FileVisitor that deletes files that are accepted by all PathFilters. Directories are ignored. + */ +public class DeletingVisitor extends SimpleFileVisitor { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Path basePath; + private final boolean testMode; + private final List pathConditions; + + /** + * 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 status logger + * at INFO level. Users can use this to do a dry run to test if their configuration works as expected. + */ + public DeletingVisitor( + final Path basePath, final List pathConditions, final boolean testMode) { + this.testMode = testMode; + this.basePath = Objects.requireNonNull(basePath, "basePath"); + this.pathConditions = Objects.requireNonNull(pathConditions, "pathConditions"); + for (final PathCondition condition : pathConditions) { + condition.beforeFileTreeWalk(); + } + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + for (final PathCondition pathFilter : pathConditions) { + final Path relative = basePath.relativize(file); + if (!pathFilter.accept(basePath, relative, attrs)) { + LOGGER.trace("Not deleting base={}, relative={}", basePath, relative); + return FileVisitResult.CONTINUE; + } + } + if (isTestMode()) { + LOGGER.info("Deleting {} (TEST MODE: file not actually deleted)", file); + } else { + delete(file); + } + 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); + } + } + + /** + * Deletes the specified file. + * + * @param file the file to delete + * @throws IOException if a problem occurred deleting the file + */ + protected void delete(final Path file) throws IOException { + LOGGER.trace("Deleting {}", file); + Files.deleteIfExists(file); + } + + /** + * 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() { + return testMode; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java index 5305c910775..a9c4f9437b0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/Duration.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.io.Serializable; @@ -29,9 +28,11 @@ *

* 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) + * @deprecated since 2.24.0 use {@link java.time.Duration} instead. */ +@Deprecated public class Duration implements Serializable, Comparable { private static final long serialVersionUID = -3756810052716342061L; @@ -64,8 +65,15 @@ public class Duration implements Serializable, Comparable { /** * The pattern for parsing. */ - private static final Pattern PATTERN = Pattern.compile("P?(?:([0-9]+)D)?" - + "(T?(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)?S)?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern PATTERN = Pattern.compile( + "P?(?:([0-9]+)D)?" + "(T?(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)?S)?)?", Pattern.CASE_INSENSITIVE); + + /** + * @since 2.24.0 + */ + public static Duration ofMillis(final long millis) { + return new Duration(millis / 1000L); + } /** * The number of seconds in the duration. @@ -78,7 +86,6 @@ public class Duration implements Serializable, Comparable { * @param seconds the length of the duration in seconds, positive or negative */ private Duration(final long seconds) { - super(); this.seconds = seconds; } @@ -101,7 +108,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)
@@ -132,7 +139,8 @@ public static Duration parse(final CharSequence text) {
                     try {
                         return create(daysAsSecs, hoursAsSecs, minsAsSecs, seconds);
                     } catch (final ArithmeticException ex) {
-                        throw new IllegalArgumentException("Text cannot be parsed to a Duration (overflow) " + text, ex);
+                        throw new IllegalArgumentException(
+                                "Text cannot be parsed to a Duration (overflow) " + text, ex);
                     }
                 }
             }
@@ -140,8 +148,8 @@ public static Duration parse(final CharSequence text) {
         throw new IllegalArgumentException("Text cannot be parsed to a Duration: " + text);
     }
 
-    private static long parseNumber(final CharSequence text, final String parsed, final int multiplier,
-            final String errorText) {
+    private static long parseNumber(
+            final CharSequence text, final String parsed, final int multiplier, final String errorText) {
         // regex limits to [0-9]+
         if (parsed == null) {
             return 0;
@@ -150,12 +158,13 @@ private static long parseNumber(final CharSequence text, final String parsed, fi
             final long val = Long.parseLong(parsed);
             return val * multiplier;
         } catch (final Exception ex) {
-            throw new IllegalArgumentException("Text cannot be parsed to a Duration: " + errorText + " (in " + text
-                    + ")", ex);
+            throw new IllegalArgumentException(
+                    "Text cannot be parsed to a Duration: " + errorText + " (in " + text + ")", ex);
         }
     }
 
-    private static Duration create(final long daysAsSecs, final long hoursAsSecs, final long minsAsSecs, final long secs) {
+    private static Duration create(
+            final long daysAsSecs, final long hoursAsSecs, final long minsAsSecs, final long secs) {
         return create(daysAsSecs + hoursAsSecs + minsAsSecs + secs);
     }
 
@@ -205,14 +214,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 +256,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..e7bb2376db7 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 @@ -1,25 +1,28 @@ /* * 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.logging.log4j.core.appender.rolling.action; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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; @@ -101,6 +104,9 @@ public boolean isRenameEmptyFiles() { * @param renameEmptyFiles if true, rename file even if empty, otherwise delete empty files. * @return true if successfully renamed. */ + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") public static boolean execute(final File source, final File destination, final boolean renameEmptyFiles) { if (renameEmptyFiles || (source.length() > 0)) { final File parent = destination.getParentFile(); @@ -116,67 +122,109 @@ 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 { - Files.copy(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()), + Files.copy( + Paths.get(source.getAbsolutePath()), + Paths.get(destination.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); try { Files.delete(Paths.get(source.getAbsolutePath())); result = true; - LOGGER.trace("Renamed file {} to {} using copy and delete", - source.getAbsolutePath(), destination.getAbsolutePath()); + LOGGER.trace( + "Renamed file {} to {} using copy and delete", + source.getAbsolutePath(), + destination.getAbsolutePath()); } catch (final IOException exDelete) { - LOGGER.error("Unable to delete file {}: {} {}", source.getAbsolutePath(), - exDelete.getClass().getName(), exDelete.getMessage()); + LOGGER.error( + "Unable to delete file {}: {} {}", + source.getAbsolutePath(), + exDelete.getClass().getName(), + exDelete.getMessage()); try { + result = true; new PrintWriter(source.getAbsolutePath()).close(); - LOGGER.trace("Renamed file {} to {} with copy and truncation", - source.getAbsolutePath(), destination.getAbsolutePath()); + LOGGER.trace( + "Renamed file {} to {} with copy and truncation", + source.getAbsolutePath(), + destination.getAbsolutePath()); } catch (final IOException exOwerwrite) { - LOGGER.error("Unable to overwrite file {}: {} {}", - source.getAbsolutePath(), exOwerwrite.getClass().getName(), + LOGGER.error( + "Unable to overwrite file {}: {} {}", + source.getAbsolutePath(), + exOwerwrite.getClass().getName(), exOwerwrite.getMessage()); } } } catch (final IOException exCopy) { - LOGGER.error("Unable to copy file {} to {}: {} {}", source.getAbsolutePath(), - destination.getAbsolutePath(), exCopy.getClass().getName(), exCopy.getMessage()); + LOGGER.error( + "Unable to copy file {} to {}: {} {}", + source.getAbsolutePath(), + destination.getAbsolutePath(), + exCopy.getClass().getName(), + exCopy.getMessage()); } } else { - LOGGER.trace("Renamed file {} to {} with source.renameTo", - source.getAbsolutePath(), destination.getAbsolutePath()); + LOGGER.trace( + "Renamed file {} to {} with source.renameTo", + source.getAbsolutePath(), + destination.getAbsolutePath()); } return result; } } catch (final RuntimeException ex) { - LOGGER.error("Unable to rename file {} to {}: {} {}", source.getAbsolutePath(), - destination.getAbsolutePath(), ex.getClass().getName(), ex.getMessage()); + LOGGER.error( + "Unable to rename file {} to {}: {} {}", + source.getAbsolutePath(), + destination.getAbsolutePath(), + ex.getClass().getName(), + ex.getMessage()); } } else { try { - source.delete(); + return source.delete(); } catch (final Exception exDelete) { - LOGGER.error("Unable to delete empty file {}: {} {}", source.getAbsolutePath(), - exDelete.getClass().getName(), exDelete.getMessage()); + LOGGER.error( + "Unable to delete empty file {}: {} {}", + source.getAbsolutePath(), + exDelete.getClass().getName(), + exDelete.getMessage()); } } return false; } + private static boolean moveFile(Path source, 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 - + ", renameEmptyFiles=" + renameEmptyFiles + ']'; + return FileRenameAction.class.getSimpleName() + '[' + source + " to " + destination + ", renameEmptyFiles=" + + renameEmptyFiles + ']'; } - } 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..acad1f5116f 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -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,36 @@ 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 +145,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 +154,14 @@ 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,8 +175,8 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return GzCompressAction.class.getSimpleName() + '[' + source + " to " + destination - + ", deleteSource=" + deleteSource + ']'; + return GzCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", deleteSource=" + + deleteSource + ']'; } public File getSource() { 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..49e1a2abefd 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -21,7 +21,6 @@ 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; @@ -40,13 +39,12 @@ public final class IfAccumulatedFileCount implements PathCondition { 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 +57,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) */ @@ -68,7 +66,12 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF final boolean result = ++count > threshold; final String match = result ? ">" : "<="; final String accept = result ? "ACCEPTED" : "REJECTED"; - LOGGER.trace("IfAccumulatedFileCount {}: {} count '{}' {} threshold '{}'", accept, relativePath, count, match, + LOGGER.trace( + "IfAccumulatedFileCount {}: {} count '{}' {} threshold '{}'", + accept, + relativePath, + count, + match, threshold); if (result) { return IfAll.accept(nestedConditions, basePath, relativePath, attrs); @@ -78,7 +81,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,16 +92,16 @@ 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) { - // @formatter:on + // @formatter:on if (threshold == Integer.MAX_VALUE) { LOGGER.error("IfAccumulatedFileCount invalid or missing threshold value."); 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..3b493a32ddd 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -21,7 +21,6 @@ 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; @@ -41,13 +40,12 @@ public final class IfAccumulatedFileSize implements PathCondition { 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 +58,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) */ @@ -70,8 +68,13 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF final boolean result = accumulatedSize > thresholdBytes; final String match = result ? ">" : "<="; final String accept = result ? "ACCEPTED" : "REJECTED"; - LOGGER.trace("IfAccumulatedFileSize {}: {} accumulated size '{}' {} thresholdBytes '{}'", accept, relativePath, - accumulatedSize, match, thresholdBytes); + LOGGER.trace( + "IfAccumulatedFileSize {}: {} accumulated size '{}' {} thresholdBytes '{}'", + accept, + relativePath, + accumulatedSize, + match, + thresholdBytes); if (result) { return IfAll.accept(nestedConditions, basePath, relativePath, attrs); } @@ -80,7 +83,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,16 +94,16 @@ public void beforeFileTreeWalk() { /** * Create an IfAccumulatedFileSize condition. - * - * @param threshold The threshold accumulated file size from which files will be deleted. + * + * @param size 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) { - // @formatter:on + // @formatter:on if (size == null) { LOGGER.error("IfAccumulatedFileSize missing mandatory size threshold."); 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..e4a5e3596ff 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -20,11 +20,11 @@ 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; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; /** * Composite {@code PathCondition} that only accepts objects that are accepted by all component conditions. @@ -45,7 +45,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 +59,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. @@ -67,8 +67,8 @@ public boolean accept(final Path baseDir, final Path relativePath, final BasicFi * @return {@code true} if all the specified conditions accept the specified path, {@code false} otherwise * @throws NullPointerException if any of the parameters is {@code null} */ - public static boolean accept(final PathCondition[] list, final Path baseDir, final Path relativePath, - final BasicFileAttributes attrs) { + public static boolean accept( + final PathCondition[] list, final Path baseDir, final Path relativePath, final BasicFileAttributes attrs) { for (final PathCondition component : list) { if (!component.accept(baseDir, relativePath, attrs)) { return false; @@ -79,7 +79,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 +89,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,13 +100,14 @@ 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( - @PluginElement("PathConditions") final PathCondition... components) { + public static IfAll createAndCondition( + @PluginElement("PathConditions") @Required(message = "No components provided for IfAll") + 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..496f5a701bd 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -20,11 +20,11 @@ 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; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; /** * Composite {@code PathCondition} that accepts objects that are accepted by any component conditions. @@ -70,13 +70,14 @@ 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( - @PluginElement("PathConditions") final PathCondition... components) { + public static IfAny createOrCondition( + @PluginElement("PathConditions") @Required(message = "No components provided for IfAny") + 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..c57475a0cef 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -25,7 +25,6 @@ import java.util.Collections; 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; @@ -52,20 +51,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."); + 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 +78,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 +91,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 +110,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 +123,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,12 +131,12 @@ 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("glob") final String glob, + @PluginAttribute("regex") final String regex, @PluginElement("PathConditions") final PathCondition... nestedConditions) { - // @formatter:on + // @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..e8a105edf5f 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 @@ -1,41 +1,43 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; +import java.time.Duration; 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.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.Required; import org.apache.logging.log4j.core.util.Clock; import org.apache.logging.log4j.core.util.ClockFactory; import org.apache.logging.log4j.status.StatusLogger; /** * PathCondition that accepts paths that are older than the specified duration. + * @since 2.5 */ @Plugin(name = "IfLastModified", category = Core.CATEGORY_NAME, printObject = true) public final class IfLastModified implements PathCondition { @@ -47,24 +49,21 @@ public final class IfLastModified implements PathCondition { private IfLastModified(final Duration age, final PathCondition[] nestedConditions) { this.age = Objects.requireNonNull(age, "age"); - this.nestedConditions = nestedConditions == null ? new PathCondition[0] : Arrays.copyOf(nestedConditions, - nestedConditions.length); + this.nestedConditions = PathCondition.copy(nestedConditions); } - public Duration getAge() { - return age; + /** + * @deprecated since 2.24.0. In 3.0.0 the signature will change. + */ + @Deprecated + public org.apache.logging.log4j.core.appender.rolling.action.Duration getAge() { + return org.apache.logging.log4j.core.appender.rolling.action.Duration.ofMillis(age.toMillis()); } public List getNestedConditions() { return Collections.unmodifiableList(Arrays.asList(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) - */ @Override public boolean accept(final Path basePath, final Path relativePath, final BasicFileAttributes attrs) { final FileTime fileTime = attrs.lastModifiedTime(); @@ -80,30 +79,22 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF return result; } - /* - * (non-Javadoc) - * - * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() - */ @Override public void beforeFileTreeWalk() { IfAll.beforeFileTreeWalk(nestedConditions); } /** - * 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 2.24.0 use {@link #newBuilder()} instead. */ - @PluginFactory - public static IfLastModified createAgeCondition( - // @formatter:off - @PluginAttribute("age") final Duration age, - @PluginElement("PathConditions") final PathCondition... nestedConditions) { - // @formatter:on - return new IfLastModified(age, nestedConditions); + @Deprecated + public static IfLastModified createAgeCondition( + final org.apache.logging.log4j.core.appender.rolling.action.Duration age, + final PathCondition... pathConditions) { + return newBuilder() + .setAge(Duration.ofMillis(age.toMillis())) + .setNestedConditions(pathConditions) + .build(); } @Override @@ -111,4 +102,39 @@ public String toString() { final String nested = nestedConditions.length == 0 ? "" : " AND " + Arrays.toString(nestedConditions); return "IfLastModified(age=" + age + nested + ")"; } + + /** + * @since 2.24.0 + */ + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + /** + * @since 2.24.0 + */ + public static final class Builder implements org.apache.logging.log4j.core.util.Builder { + @PluginBuilderAttribute + @Required(message = "No age provided for IfLastModified") + private org.apache.logging.log4j.core.appender.rolling.action.Duration age; + + @PluginElement("nestedConditions") + private PathCondition[] nestedConditions; + + public Builder setAge(final Duration age) { + this.age = org.apache.logging.log4j.core.appender.rolling.action.Duration.ofMillis(age.toMillis()); + return this; + } + + public Builder setNestedConditions(final PathCondition... nestedConditions) { + this.nestedConditions = nestedConditions; + return this; + } + + @Override + public IfLastModified build() { + return isValid() ? new IfLastModified(Duration.ofMillis(age.toMillis()), nestedConditions) : null; + } + } } 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..a512c5381ff 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 @@ -1,29 +1,29 @@ /* * 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.logging.log4j.core.appender.rolling.action; 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; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; /** * Wrapper {@code PathCondition} that accepts objects that are rejected by the wrapped component filter. @@ -61,13 +61,14 @@ public void beforeFileTreeWalk() { /** * Create an IfNot PathCondition. - * + * * @param condition The condition to negate. * @return An IfNot PathCondition. */ @PluginFactory - public static IfNot createNotCondition( - @PluginElement("PathConditions") final PathCondition condition) { + public static IfNot createNotCondition( + @PluginElement("PathConditions") @Required(message = "No condition provided for IfNot") + 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..02fe4cfd897 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,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.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 { + + /** + * The 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..b385599ed2d 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 @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.io.Serializable; - 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; @@ -37,7 +35,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 +45,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( + public static PathSorter createSorter( @PluginAttribute(value = "recentFirst", 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 +66,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/PathSorter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java index 2e765ab0b8c..0c8e33e93d5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSorter.java @@ -1,27 +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.appender.rolling.action; - -import java.util.Comparator; - -/** - * Defines the interface of classes that can sort Paths. - */ -public interface PathSorter extends Comparator{ - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.util.Comparator; + +/** + * Defines the interface of classes that can sort Paths. + */ +public interface PathSorter extends Comparator {} 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..f4957a123b2 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 @@ -1,59 +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.appender.rolling.action; - -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Objects; - -/** - * Tuple of a {@code Path} and {@code BasicFileAttributes}, used for sorting. - */ -public class PathWithAttributes { - - private final Path path; - private final BasicFileAttributes attributes; - - public PathWithAttributes(final Path path, final BasicFileAttributes attributes) { - this.path = Objects.requireNonNull(path, "path"); - this.attributes = Objects.requireNonNull(attributes, "attributes"); - } - - @Override - public String toString() { - return path + " (modified: " + attributes.lastModifiedTime() + ")"; - } - - /** - * Returns the path. - * - * @return the path - */ - public Path getPath() { - return path; - } - - /** - * Returns the attributes. - * - * @return the attributes - */ - public BasicFileAttributes getAttributes() { - return attributes; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.attribute.BasicFileAttributes; +import java.util.Objects; + +/** + * Tuple of a {@code Path} and {@code BasicFileAttributes}, used for sorting. + */ +public class PathWithAttributes { + + private final Path path; + private final BasicFileAttributes attributes; + + public PathWithAttributes(final Path path, final BasicFileAttributes attributes) { + this.path = Objects.requireNonNull(path, "path"); + this.attributes = Objects.requireNonNull(attributes, "attributes"); + } + + @Override + public String toString() { + return path + " (modified: " + attributes.lastModifiedTime() + ")"; + } + + /** + * Returns the path. + * + * @return the path + */ + public Path getPath() { + return path; + } + + /** + * Returns the attributes. + * + * @return the attributes + */ + public BasicFileAttributes getAttributes() { + return attributes; + } +} 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..51e038f1c13 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 @@ -1,21 +1,22 @@ /* * 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.logging.log4j.core.appender.rolling.action; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -28,7 +29,6 @@ import java.nio.file.attribute.PosixFilePermissions; 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; @@ -42,13 +42,13 @@ 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) public class PosixViewAttributeAction extends AbstractPathAction { - + /** * File permissions. */ @@ -64,10 +64,15 @@ public class PosixViewAttributeAction extends AbstractPathAction { */ private final String fileGroup; - private PosixViewAttributeAction(final String basePath, final boolean followSymbolicLinks, - final int maxDepth, final PathCondition[] pathConditions, final StrSubstitutor subst, + private PosixViewAttributeAction( + final String basePath, + final boolean followSymbolicLinks, + final int maxDepth, + final PathCondition[] pathConditions, + final StrSubstitutor subst, final Set filePermissions, - final String fileOwner, final String fileGroup) { + final String fileOwner, + final String fileGroup) { super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst); this.filePermissions = filePermissions; this.fileOwner = fileOwner; @@ -80,13 +85,13 @@ public static Builder newBuilder() { } /** - * 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 { @PluginConfiguration private Configuration configuration; - + private StrSubstitutor subst; @PluginBuilderAttribute @@ -114,14 +119,19 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde private String fileGroup; @Override + @SuppressFBWarnings( + value = "OVERLY_PERMISSIVE_FILE_PERMISSION", + justification = "File permissions are specified in a configuration file.") public PosixViewAttributeAction build() { if (Strings.isEmpty(basePath)) { LOGGER.error("Posix file attribute view action not valid because base path is empty."); return null; } - if (filePermissions == null && Strings.isEmpty(filePermissionsString) - && Strings.isEmpty(fileOwner) && Strings.isEmpty(fileGroup)) { + if (filePermissions == null + && Strings.isEmpty(filePermissionsString) + && Strings.isEmpty(fileOwner) + && Strings.isEmpty(fileGroup)) { LOGGER.error("Posix file attribute view not valid because nor permissions, user or group defined."); return null; } @@ -131,10 +141,17 @@ public PosixViewAttributeAction build() { return null; } - return new PosixViewAttributeAction(basePath, followLinks, maxDepth, pathConditions, + return new PosixViewAttributeAction( + basePath, + followLinks, + maxDepth, + pathConditions, subst != null ? subst : configuration.getStrSubstitutor(), - filePermissions != null ? filePermissions : - filePermissionsString != null ? PosixFilePermissions.fromString(filePermissionsString) : null, + filePermissions != null + ? filePermissions + : filePermissionsString != null + ? PosixFilePermissions.fromString(filePermissionsString) + : null, fileOwner, fileGroup); } @@ -144,8 +161,9 @@ public PosixViewAttributeAction build() { * * @param configuration {@link AbstractPathAction#getStrSubstitutor()} * @return This builder + * @since 2.26.0 */ - public Builder withConfiguration(final Configuration configuration) { + public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } @@ -155,18 +173,20 @@ public Builder withConfiguration(final Configuration configuration) { * * @param subst {@link AbstractPathAction#getStrSubstitutor()} * @return This builder + * @since 2.26.0 */ - 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 + * @since 2.26.0 */ - public Builder withBasePath(final String basePath) { + public Builder setBasePath(final String basePath) { this.basePath = basePath; return this; } @@ -175,18 +195,20 @@ public Builder withBasePath(final String basePath) { * True to allow synonyms links during search of eligible files. * @param followLinks Follow synonyms links * @return This builder + * @since 2.26.0 */ - 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 + * @since 2.26.0 */ - public Builder withMaxDepth(final int maxDepth) { + public Builder setMaxDepth(final int maxDepth) { this.maxDepth = maxDepth; return this; } @@ -196,22 +218,24 @@ public Builder withMaxDepth(final int maxDepth) { * * @param pathConditions {@link AbstractPathAction#getPathConditions()} * @return This builder + * @since 2.26.0 */ - 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 *

r--r--r-- * @param filePermissionsString Permissions to apply * @return This builder + * @since 2.26.0 */ - public Builder withFilePermissionsString(final String filePermissionsString) { + public Builder setFilePermissionsString(final String filePermissionsString) { this.filePermissionsString = filePermissionsString; return this; } @@ -220,8 +244,9 @@ public Builder withFilePermissionsString(final String filePermissionsString) { * Define file permissions to apply during action execution eligible files. * @param filePermissions Permissions to apply * @return This builder + * @since 2.26.0 */ - public Builder withFilePermissions(final Set filePermissions) { + public Builder setFilePermissions(final Set filePermissions) { this.filePermissions = filePermissions; return this; } @@ -230,8 +255,9 @@ public Builder withFilePermissions(final Set filePermission * Define file owner to apply during action execution eligible files. * @param fileOwner File owner * @return This builder + * @since 2.26.0 */ - public Builder withFileOwner(final String fileOwner) { + public Builder setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return this; } @@ -240,7 +266,98 @@ public Builder withFileOwner(final String fileOwner) { * Define file group to apply during action execution eligible files. * @param fileGroup File group * @return This builder + * @since 2.26.0 + */ + public Builder setFileGroup(final String fileGroup) { + this.fileGroup = fileGroup; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfiguration(Configuration)}. + */ + @Deprecated + public Builder withConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setSubst(StrSubstitutor)}. + */ + @Deprecated + public Builder withSubst(final StrSubstitutor subst) { + this.subst = subst; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setBasePath(String)}. + */ + @Deprecated + public Builder withBasePath(final String basePath) { + this.basePath = basePath; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFollowLinks(boolean)}. + */ + @Deprecated + public Builder withFollowLinks(final boolean followLinks) { + this.followLinks = followLinks; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setMaxDepth(int)}. + */ + @Deprecated + public Builder withMaxDepth(final int maxDepth) { + this.maxDepth = maxDepth; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setPathConditions(PathCondition[])}. */ + @Deprecated + public Builder withPathConditions(final PathCondition[] pathConditions) { + this.pathConditions = pathConditions; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFilePermissionsString(String)}. + */ + @Deprecated + public Builder withFilePermissionsString(final String filePermissionsString) { + this.filePermissionsString = filePermissionsString; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFilePermissions(Set)}. + */ + @Deprecated + public Builder withFilePermissions(final Set filePermissions) { + this.filePermissions = filePermissions; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFileOwner(String)}. + */ + @Deprecated + public Builder withFileOwner(final String fileOwner) { + this.fileOwner = fileOwner; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setFileGroup(String)}. + */ + @Deprecated public Builder withFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return this; @@ -248,15 +365,14 @@ public Builder withFileGroup(final String fileGroup) { } @Override - protected FileVisitor createFileVisitor(final Path basePath, - final List conditions) { + protected FileVisitor createFileVisitor(final Path basePath, final List conditions) { return new SimpleFileVisitor() { @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 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 +383,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 +403,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 */ @@ -303,5 +419,4 @@ public String toString() { + ", getMaxDepth()=" + getMaxDepth() + ", getPathConditions()=" + getPathConditions() + "]"; } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptCondition.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptCondition.java index 9d728c0339f..b87eff7ccc3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptCondition.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ScriptCondition.java @@ -1,28 +1,25 @@ /* * 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.logging.log4j.core.appender.rolling.action; import java.nio.file.Path; import java.util.List; import java.util.Objects; - import javax.script.SimpleBindings; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.config.Configuration; @@ -37,8 +34,8 @@ /** * A condition of the {@link DeleteAction} where a user-provided script selects the files to delete from a provided - * list. The specified script may be a {@link Script}, a {@link ScriptFile} or a {@link ScriptRef}. - * + * list. The specified script may be a {@link org.apache.logging.log4j.core.script.Script}, a {@link ScriptFile} or a {@link ScriptRef}. + * * @see #createCondition(AbstractScript, Configuration) */ @Plugin(name = "ScriptCondition", category = Core.CATEGORY_NAME, printObject = true) @@ -50,27 +47,25 @@ public class ScriptCondition { /** * Constructs a new ScriptCondition. - * + * * @param script the script that can select files to delete * @param configuration configuration containing the StrSubstitutor passed to the script */ public ScriptCondition(final AbstractScript script, final Configuration configuration) { this.script = Objects.requireNonNull(script, "script"); this.configuration = Objects.requireNonNull(configuration, "configuration"); - if (!(script instanceof ScriptRef)) { - configuration.getScriptManager().addScript(script); - } } /** * Executes the script - * - * @param baseDir - * @param candidates - * @return + * + * @param basePath base directory for files to delete + * @param candidates a list of paths, that can be deleted by the script + * @return a list of paths selected to delete by the script execution */ @SuppressWarnings("unchecked") - public List selectFilesToDelete(final Path basePath, final List candidates) { + public List selectFilesToDelete( + final Path basePath, final List candidates) { final SimpleBindings bindings = new SimpleBindings(); bindings.put("basePath", basePath); bindings.put("pathList", candidates); @@ -78,40 +73,49 @@ public List selectFilesToDelete(final Path basePath, final L bindings.put("configuration", configuration); bindings.put("substitutor", configuration.getStrSubstitutor()); bindings.put("statusLogger", LOGGER); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); + final Object object = configuration.getScriptManager().execute(script.getId(), bindings); return (List) object; } /** * Creates the ScriptCondition. - * - * @param script The script to run. This may be a {@link Script}, a {@link ScriptFile} or a {@link ScriptRef}. The + * + * @param script The script to run. This may be a {@link org.apache.logging.log4j.core.script.Script}, a {@link ScriptFile} or a {@link ScriptRef}. The * script must return a {@code List}. When the script is executed, it is provided the * following bindings: *

    *
  • basePath - the directory from where the {@link DeleteAction Delete} action started scanning for * files to delete. Can be used to relativize the paths in the pathList.
  • - *
  • pathList - a {@code java.util.List} containing {@link PathWithAttribute} objects. (The script is + *
  • pathList - a {@code java.util.List} containing {@link org.apache.logging.log4j.core.appender.rolling.action.PathWithAttributes} objects. (The script is * free to modify and return this list.)
  • - *
  • substitutor - a {@link StrSubstitutor} that can be used to look up variables embedded in the base - * dir or other properties - *
  • statusLogger - the {@link StatusLogger} that can be used to log events during script execution + *
  • substitutor - a {@link org.apache.logging.log4j.core.lookup.StrSubstitutor} that can be used to look up variables embedded in the base + * dir or other properties
  • + *
  • statusLogger - the {@link StatusLogger} that can be used to log events during script execution
  • *
  • any properties declared in the configuration
  • *
* @param configuration the configuration * @return A ScriptCondition. */ @PluginFactory - public static ScriptCondition createCondition(@PluginElement("Script") final AbstractScript script, + public static ScriptCondition createCondition( + @PluginElement("Script") final AbstractScript script, @PluginConfiguration final Configuration configuration) { if (script == null) { LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptCondition"); return null; } + if (configuration.getScriptManager() == null) { + LOGGER.error("Script support is not enabled"); + return null; + } if (script instanceof ScriptRef) { - if (configuration.getScriptManager().getScript(script.getName()) == null) { - LOGGER.error("ScriptCondition: No script with name {} has been declared.", script.getName()); + if (configuration.getScriptManager().getScript(script.getId()) == null) { + LOGGER.error("ScriptCondition: No script with name {} has been declared.", script.getId()); + return null; + } + } else { + if (!configuration.getScriptManager().addScript(script)) { return null; } } 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..88a02cf3726 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 @@ -1,58 +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.appender.rolling.action; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * FileVisitor that sorts files. - */ -public class SortingVisitor extends SimpleFileVisitor { - - 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 - */ - public SortingVisitor(final PathSorter sorter) { - this.sorter = Objects.requireNonNull(sorter, "sorter"); - } - - @Override - public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException { - collected.add(new PathWithAttributes(path, attrs)); - return FileVisitResult.CONTINUE; - } - - public List getSortedPaths() { - Collections.sort(collected, sorter); - return collected; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.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; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * FileVisitor that sorts files. + */ +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 SortingVisitor. + * + * @param sorter Interface implementation which can sort paths. + */ + public SortingVisitor(final PathSorter sorter) { + this.sorter = Objects.requireNonNull(sorter, "sorter"); + } + + @Override + public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException { + 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..f29a7391ceb 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.rolling.action; @@ -92,8 +92,8 @@ public boolean execute() throws IOException { * @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 level) throws IOException { + public static boolean execute( + final File source, final File destination, final boolean deleteSource, final int level) throws IOException { if (source.exists()) { try (final FileInputStream fis = new FileInputStream(source); final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destination))) { @@ -132,8 +132,8 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return ZipCompressAction.class.getSimpleName() + '[' + source + " to " + destination - + ", level=" + level + ", deleteSource=" + deleteSource + ']'; + return ZipCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", level=" + level + + ", deleteSource=" + deleteSource + ']'; } public File getSource() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/package-info.java index cdc5afe241d..37370530300 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/package-info.java @@ -17,4 +17,9 @@ /** * Support classes for the Rolling File Appender. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.appender.rolling.action; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/package-info.java index 899fef054f8..9f4ea078048 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/package-info.java @@ -17,4 +17,9 @@ /** * Rolling File Appender and support classes. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.appender.rolling; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..b49cde9833c 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 @@ -1,27 +1,28 @@ /* * 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.logging.log4j.core.appender.routing; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + 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; @@ -41,7 +42,7 @@ 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()); } } @@ -93,7 +95,6 @@ public void update(final String key, final LogEvent event) { } } } - } @Override @@ -123,16 +124,16 @@ 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, - @PluginConfiguration final Configuration configuration) { + @PluginAttribute("timeToLive") final String timeToLive, + @PluginAttribute("checkInterval") final String checkInterval, + @PluginAttribute("timeUnit") final String timeUnit, + @PluginConfiguration final Configuration configuration) { if (timeToLive == null) { LOGGER.error("A timeToLive value is required"); @@ -143,7 +144,7 @@ public static PurgePolicy createPurgePolicy( units = TimeUnit.MINUTES; } else { try { - units = TimeUnit.valueOf(timeUnit.toUpperCase()); + units = TimeUnit.valueOf(toRootUpperCase(timeUnit)); } catch (final Exception ex) { LOGGER.error("Invalid timeUnit value {}. timeUnit set to MINUTES", timeUnit, ex); units = TimeUnit.MINUTES; @@ -155,7 +156,7 @@ public static PurgePolicy createPurgePolicy( LOGGER.error("timeToLive must be positive. timeToLive set to 0"); ttl = 0; } - + long ci; if (checkInterval == null) { ci = ttl; @@ -174,5 +175,4 @@ public static PurgePolicy createPurgePolicy( public String toString() { return "timeToLive=" + timeToLive; } - } 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..558dffaa4ab 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.routing; @@ -23,23 +23,22 @@ */ public interface PurgePolicy { - /** - * Activates purging appenders - */ - void purge(); - - /** - * - * @param routed appender key - * @param event - */ - void update(String key, LogEvent event); + /** + * Activates purging appenders. Note that {@link PurgePolicy} implementations are responsible for invoking + * this method themselves. + */ + void purge(); - /** - * Initializes with routing appender - * - * @param routingAppender - */ - void initialize(RoutingAppender routingAppender); + /** + * @param key routed appender key + * @param event Provides contextual information about a logged message. + */ + void update(String key, LogEvent event); + /** + * Initializes with routing appender + * + * @param routingAppender the routed appender for purging + */ + 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..4a9644bbe8f 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.routing; @@ -103,11 +103,9 @@ public static Route createRoute( LOGGER.error("A route cannot be configured with an appender reference and an appender definition"); return null; } - } else { - if (appenderRef == null) { - LOGGER.error("A route must specify an appender reference or an appender definition"); - return null; - } + } else if (appenderRef == null) { + LOGGER.error("A route must specify an appender reference or an appender definition"); + return null; } return new Route(node, appenderRef, key); } 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..b08503d0e1e 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.appender.routing; @@ -20,9 +20,7 @@ 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; @@ -35,6 +33,7 @@ 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; +import org.apache.logging.log4j.core.script.ScriptRef; import org.apache.logging.log4j.status.StatusLogger; /** @@ -45,14 +44,14 @@ 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.core.util.Builder { - @PluginConfiguration + @PluginConfiguration private Configuration configuration; - @PluginAttribute("pattern") + @PluginAttribute("pattern") private String pattern; - + @PluginElement("Script") private AbstractScript patternScript; @@ -66,14 +65,24 @@ 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); + if (configuration.getScriptManager() == null) { + LOGGER.error("Script support is not enabled"); + return null; + } + if (!configuration.getScriptManager().addScript(patternScript)) { + if (!(patternScript instanceof ScriptRef)) { + if (!getConfiguration().getScriptManager().addScript(patternScript)) { + return null; + } + } + } } } return new Routes(configuration, patternScript, pattern, routes); @@ -95,26 +104,73 @@ public Route[] getRoutes() { return routes; } - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + /** + * @since 2.26.0 + */ + public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } - public Builder withPattern(@SuppressWarnings("hiding") final String pattern) { + /** + * @since 2.26.0 + */ + public Builder setPattern(final String pattern) { this.pattern = pattern; return this; } - public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) { + /** + * @since 2.26.0 + */ + public Builder setPatternScript(final AbstractScript patternScript) { this.patternScript = patternScript; return this; } - public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) { + /** + * @since 2.26.0 + */ + public Builder setRoutes(final Route[] routes) { + this.routes = routes; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setConfiguration(Configuration)}. + */ + @Deprecated + public Builder withConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setPattern(String)}. + */ + @Deprecated + public Builder withPattern(final String pattern) { + this.pattern = pattern; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setPatternScript(AbstractScript)}. + */ + @Deprecated + public Builder withPatternScript(final AbstractScript patternScript) { + this.patternScript = patternScript; + return this; + } + + /** + * @deprecated since 2.26.0 use {@link #setRoutes(Route[])}. + */ + @Deprecated + public Builder withRoutes(final Route[] routes) { this.routes = routes; return this; } - } private static final Logger LOGGER = StatusLogger.getLogger(); @@ -127,9 +183,7 @@ public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) { * @deprecated since 2.7; use {@link #newBuilder()}. */ @Deprecated - public static Routes createRoutes( - final String pattern, - final Route... routes) { + public static Routes createRoutes(final String pattern, final Route... routes) { if (routes == null || routes.length == 0) { LOGGER.error("No routes configured"); return null; @@ -141,17 +195,21 @@ public static Routes createRoutes( public static Builder newBuilder() { return new Builder(); } - + private final Configuration configuration; - + private final String pattern; private final AbstractScript 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 AbstractScript patternScript, + final String pattern, + final Route... routes) { this.configuration = configuration; this.patternScript = patternScript; this.pattern = pattern; @@ -170,7 +228,7 @@ public String getPattern(final LogEvent event, final ConcurrentMap> 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((Map) (Map) createdAppenders); + private final ConcurrentMap referencedAppenders = new ConcurrentHashMap<>(); private final RewritePolicy rewritePolicy; private final PurgePolicy purgePolicy; private final AbstractScript defaultRouteScript; private final ConcurrentMap scriptStaticVariables = new ConcurrentHashMap<>(); - 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); + 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, + final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.routes = routes; this.configuration = configuration; this.rewritePolicy = rewritePolicy; @@ -171,10 +247,9 @@ public void start() { error("No Configuration defined for RoutingAppender; required for Script element."); } else { final ScriptManager scriptManager = configuration.getScriptManager(); - scriptManager.addScript(defaultRouteScript); final Bindings bindings = scriptManager.createBindings(defaultRouteScript); bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables); - final Object object = scriptManager.execute(defaultRouteScript.getName(), bindings); + final Object object = scriptManager.execute(defaultRouteScript.getId(), bindings); final Route route = routes.getRoute(Objects.toString(object, null)); if (route != null) { defaultRoute = route; @@ -187,7 +262,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"); } @@ -200,15 +275,13 @@ public void start() { 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(); - } + if (appender instanceof LifeCycle2) { + ((LifeCycle2) appender).stop(timeout, timeUnit); + } else { + appender.stop(); } } setStopped(); @@ -221,20 +294,33 @@ 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 +332,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,13 +343,25 @@ 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()) { @@ -282,8 +381,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,13 +395,26 @@ 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 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 + " key already deleted"); + LOGGER.debug("Route with {} key already deleted", key); } } @@ -333,7 +449,8 @@ public static RoutingAppender createAppender( LOGGER.error("No routes defined for RoutingAppender"); return null; } - return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null); + return new RoutingAppender( + name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null, null); } public Route getDefaultRoute() { @@ -363,4 +480,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 abstract static 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/appender/routing/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/package-info.java index 3e295dde985..044a76ea32b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/package-info.java @@ -17,4 +17,9 @@ /** * Apache Flume Appender. Requires the user specifically include Flume and its dependencies. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.appender.routing; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..dd84b23c528 --- /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..b9042fbbd22 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 @@ -1,24 +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. */ package org.apache.logging.log4j.core.async; 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; 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..1b15ce3ed9e 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 @@ -1,23 +1,25 @@ /* * 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.logging.log4j.core.async; +import aQute.bnd.annotation.baseline.BaselineIgnore; +import com.lmax.disruptor.EventTranslatorVararg; +import com.lmax.disruptor.dsl.Disruptor; import java.util.List; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -26,6 +28,7 @@ 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; @@ -41,14 +44,11 @@ import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringMap; -import com.lmax.disruptor.EventTranslatorVararg; -import com.lmax.disruptor.dsl.Disruptor; - /** * 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 @@ -63,6 +63,10 @@ * and they will flush to disk at the end of each batch. This means that even 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. */ +// We changed the constructor from `public` to package-private. +// The constructor was effectively package-private, since the {@link AsyncLoggerDisruptor} class in its signature is +// package-private, without any public implementation. +@BaselineIgnore("2.24.3") public class AsyncLogger extends Logger implements EventTranslatorVararg { // Implementation note: many methods in this class are tuned for performance. MODIFY WITH CARE! // Specifically, try to keep the hot methods to 35 bytecodes or less: @@ -70,7 +74,10 @@ 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); publish(translator); } @@ -170,31 +277,72 @@ 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, - translator.thrown); + 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); + 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 Level level, final Marker marker, final Message message, final Throwable thrown) { + 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, // + 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 // + ); + } + + private void initTranslator( + final RingBufferLogEventTranslator translator, + final String fqcn, + 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, @@ -205,14 +353,7 @@ private void initTranslator(final RingBufferLogEventTranslator translator, final calcLocationIfRequested(fqcn), // CLOCK, // nanoClock // - ); - } - - private void initTranslatorThreadValues(final RingBufferLogEventTranslator translator) { - // constant check should be optimized out when using default (CACHED) - if (THREAD_NAME_CACHING_STRATEGY == ThreadNameCachingStrategy.UNCACHED) { - translator.updateThreadValues(); - } + ); } /** @@ -240,8 +381,56 @@ private StackTraceElement calcLocationIfRequested(final String fqcn) { * @param message the log message * @param thrown a {@code Throwable} or {@code null} */ - private void logWithVarargTranslator(final String fqcn, final Level level, final Marker marker, - final Message message, final Throwable thrown) { + private void logWithVarargTranslator( + final String fqcn, 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); + } + StackTraceElement location; + // calls the translateTo method on this AsyncLogger + if (!disruptor + .getRingBuffer() + .tryPublishEvent( + this, + this, // asyncLogger: 0 + (location = calcLocationIfRequested(fqcn)), // location: 1 + fqcn, // 2 + level, // 3 + marker, // 4 + message, // 5 + thrown)) { // 6 + handleRingBufferFull(location, fqcn, level, marker, message, thrown); + } + } + + /** + * 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(); @@ -253,16 +442,17 @@ private void logWithVarargTranslator(final String fqcn, final Level level, final if (!isReused(message)) { InternalAsyncUtil.makeMessageImmutable(message); } - StackTraceElement location = null; // calls the translateTo method on this AsyncLogger - if (!disruptor.getRingBuffer().tryPublishEvent(this, - this, // asyncLogger: 0 - (location = calcLocationIfRequested(fqcn)), // location: 1 - fqcn, // 2 - level, // 3 - marker, // 4 - message, // 5 - thrown)) { // 6 + 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); } } @@ -288,12 +478,24 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin final Thread currentThread = Thread.currentThread(); final String threadName = THREAD_NAME_CACHING_STRATEGY.getThreadName(); - event.setValues(asyncLogger, asyncLogger.getName(), marker, fqcn, level, message, thrown, + 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()), - contextStack, currentThread.getId(), threadName, currentThread.getPriority(), location, - CLOCK, nanoClock); + contextStack, + currentThread.getId(), + threadName, + currentThread.getPriority(), + location, + CLOCK, + nanoClock); } /** @@ -306,30 +508,31 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin * @param message log message * @param thrown optional exception */ - void logMessageInCurrentThread(final String fqcn, final Level level, final Marker marker, - final Message message, final Throwable thrown) { + void logMessageInCurrentThread( + final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { // bypass RingBuffer and invoke Appender directly final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); strategy.log(this, getName(), fqcn, marker, level, message, thrown); } - private void handleRingBufferFull(final StackTraceElement location, - final String fqcn, - final Level level, - final Marker marker, - final Message msg, - final Throwable thrown) { + private void handleRingBufferFull( + final StackTraceElement location, + final String fqcn, + final Level level, + final Marker marker, + final Message msg, + 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, - this, // asyncLogger: 0 + loggerDisruptor.enqueueLogMessageWhenQueueFull( + this, this, // asyncLogger: 0 location, // location: 1 fqcn, // 2 level, // 3 @@ -355,29 +558,42 @@ 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.evaluate(privateConfig.config.getStrSubstitutor()); + 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..92d351e2de9 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 @@ -1,25 +1,25 @@ /* * 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.logging.log4j.core.async; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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; @@ -32,13 +32,12 @@ 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.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.config.plugins.validation.constraints.Required; 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.spi.AbstractLogger; import org.apache.logging.log4j.util.Strings; @@ -49,10 +48,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,30 +68,104 @@ * 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) +@Plugin(name = "AsyncLogger", category = Node.CATEGORY, printObject = true) public class AsyncLoggerConfig extends LoggerConfig { + @PluginBuilderFactory + 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(); + final LevelAndRefs container = + LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), getConfig()); + return new AsyncLoggerConfig( + name, + container.refs, + getFilter(), + container.level, + isAdditivity(), + getProperties(), + getConfig(), + shouldIncludeLocation(getIncludeLocation())); + } + } + + private static final ThreadLocal ASYNC_LOGGER_ENTERED = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + private final AsyncLoggerConfigDelegate delegate; - protected AsyncLoggerConfig(final String name, - final List appenders, final Filter filter, - final Level level, final boolean additive, - final Property[] properties, final Configuration config, + 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) { - super(name, appenders, filter, level, additive, properties, config, - includeLocation); + super(name, appenders, filter, level, additive, properties, config, includeLocation); delegate = config.getAsyncLoggerConfigDelegate(); delegate.setLogEventFactory(getLogEventFactory()); } - /** - * Passes on the event to a separate thread that will call - * {@link #asyncCallAppenders(LogEvent)}. - */ + // package-protected for testing + AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { + return delegate; + } + + @Override + @SuppressWarnings("BoxedPrimitiveEquality") + 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 { + if (!isFiltered(event)) { + // Detect the first time we encounter an AsyncLoggerConfig. We must log + // to all non-async loggers first. + processLogEvent(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); + } + } + @Override protected void callAppenders(final LogEvent event) { - populateLazilyInitializedFields(event); + super.callAppenders(event); + } + private void logToAsyncDelegate(final LogEvent event) { + // Passes on the event to a separate thread that will call + // asyncCallAppenders(LogEvent). + populateLazilyInitializedFields(event); if (!delegate.tryEnqueue(event, this)) { handleQueueFull(event); } @@ -102,8 +174,8 @@ protected void callAppenders(final LogEvent 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()); @@ -113,20 +185,24 @@ private void handleQueueFull(final LogEvent event) { private void populateLazilyInitializedFields(final LogEvent event) { event.getSource(); + event.getThreadId(); event.getThreadName(); + event.getThreadPriority(); } - 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) { + // skip the filter, which was already called on the logging thread + processLogEvent(event, LoggerConfigPredicate.ASYNCHRONOUS_ONLY); } private String displayName() { @@ -171,17 +247,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; @@ -192,53 +269,160 @@ public static LoggerConfig createLogger( try { level = Level.toLevel(levelName, Level.ERROR); } catch (final Exception ex) { - LOGGER.error( - "Invalid Log level specified: {}. Defaulting to Error", - levelName); + LOGGER.error("Invalid Log level specified: {}. Defaulting to Error", levelName); level = Level.ERROR; } final String name = loggerName.equals(LoggerConfig.ROOT) ? Strings.EMPTY : loggerName; final boolean additive = Booleans.parseBoolean(additivity, true); - return new AsyncLoggerConfig(name, appenderRefs, filter, level, - additive, properties, config, includeLocation(includeLocation)); + return new AsyncLoggerConfig( + name, + appenderRefs, + filter, + level, + additive, + properties, + config, + shouldIncludeLocation(includeLocation)); } - // Note: for asynchronous loggers, includeLocation default is FALSE + /** + * 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 + @SuppressFBWarnings("HSM_HIDING_METHOD") + public static LoggerConfig createLogger( + @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) { + final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; + return new AsyncLoggerConfig( + name, + Arrays.asList(refs), + filter, + level, + additivity, + properties, + config, + shouldIncludeLocation(includeLocation)); + } + + /** + * @deprecated since 2.25.0. The method will become private in version 3.0. + */ + @Deprecated + @SuppressFBWarnings(value = "HSM_HIDING_METHOD", justification = "Should be private.") protected static boolean includeLocation(final String includeLocationConfigValue) { + return shouldIncludeLocation(includeLocationConfigValue); + } + + // Note: for asynchronous loggers, includeLocation default is FALSE + private static boolean shouldIncludeLocation(final String includeLocationConfigValue) { return Boolean.parseBoolean(includeLocationConfigValue); } /** * An asynchronous root Logger. */ - @Plugin(name = "asyncRoot", category = Core.CATEGORY_NAME, printObject = true) + @Plugin(name = "AsyncRoot", category = Core.CATEGORY_NAME, printObject = true) public static class RootLogger extends LoggerConfig { - @PluginFactory + @PluginBuilderFactory + public static > B newAsyncRootBuilder() { + return new Builder().asBuilder(); + } + + public static class Builder> extends RootLogger.Builder { + + @Override + public LoggerConfig build() { + final LevelAndRefs container = + LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), getConfig()); + return new AsyncLoggerConfig( + LogManager.ROOT_LOGGER_NAME, + container.refs, + getFilter(), + container.level, + isAdditivity(), + getProperties(), + getConfig(), + shouldIncludeLocation(getIncludeLocation())); + } + } + + /** + * @deprecated use {@link #createLogger(String, Level, String, AppenderRef[], Property[], Configuration, Filter)} + */ + @Deprecated 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 String additivity, + final String levelName, + final String includeLocation, + final AppenderRef[] refs, + final Property[] properties, + final Configuration config, + final Filter filter) { final List appenderRefs = Arrays.asList(refs); - Level level; + Level level = null; try { level = Level.toLevel(levelName, Level.ERROR); } catch (final Exception ex) { - LOGGER.error( - "Invalid Log level specified: {}. Defaulting to Error", - levelName); + LOGGER.error("Invalid Log level specified: {}. Defaulting to Error", levelName); level = Level.ERROR; } final boolean additive = Booleans.parseBoolean(additivity, true); + return new AsyncLoggerConfig( + LogManager.ROOT_LOGGER_NAME, + appenderRefs, + filter, + level, + additive, + properties, + config, + shouldIncludeLocation(includeLocation)); + } - return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, - appenderRefs, filter, level, additive, properties, config, - AsyncLoggerConfig.includeLocation(includeLocation)); + /** + * + */ + @Deprecated + public static LoggerConfig createLogger( + @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) { + 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, + shouldIncludeLocation(includeLocation)); } } } 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..1bf13f7dd3d 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 @@ -1,54 +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. */ 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..b68e39b10a0 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 @@ -1,61 +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.core.LogEvent; -import org.apache.logging.log4j.core.impl.LogEventFactory; -import org.apache.logging.log4j.core.jmx.RingBufferAdmin; - -/** - * Encapsulates the mechanism used to log asynchronously. There is one delegate per configuration, which is shared by - * all AsyncLoggerConfig objects in the configuration. - */ -public interface AsyncLoggerConfigDelegate { - - /** - * Creates and returns a new {@code RingBufferAdmin} that instruments the ringbuffer of this - * {@code AsyncLoggerConfig}. - * - * @param contextName name of the {@code LoggerContext} - * @param loggerConfigName name of the logger config - * @return the RingBufferAdmin that instruments the ringbuffer - */ - RingBufferAdmin createRingBufferAdmin(final String contextName, final String loggerConfigName); - - /** - * Returns the {@code EventRoute} for the event with the specified level. - * - * @param level the level of the event to log - * @return the {@code EventRoute} - */ - EventRoute getEventRoute(final Level level); - - 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 - * for all AsyncLoggerConfigs that use this delegate. - * - * @param logEventFactory the factory used - */ - void setLogEventFactory(LogEventFactory logEventFactory); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.core.LogEvent; +import org.apache.logging.log4j.core.impl.LogEventFactory; +import org.apache.logging.log4j.core.jmx.RingBufferAdmin; + +/** + * Encapsulates the mechanism used to log asynchronously. There is one delegate per configuration, which is shared by + * all AsyncLoggerConfig objects in the configuration. + */ +public interface AsyncLoggerConfigDelegate { + + /** + * Creates and returns a new {@code RingBufferAdmin} that instruments the ringbuffer of this + * {@code AsyncLoggerConfig}. + * + * @param contextName name of the {@code LoggerContext} + * @param loggerConfigName name of the logger config + * @return the RingBufferAdmin that instruments the ringbuffer + */ + RingBufferAdmin createRingBufferAdmin(final String contextName, final String loggerConfigName); + + /** + * Returns the {@code EventRoute} for the event with the specified level. + * + * @param level the level of the event to log + * @return the {@code EventRoute} + */ + 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 invoked multiple times + * for all AsyncLoggerConfigs that use this delegate. + * + * @param logEventFactory the factory used + */ + void setLogEventFactory(LogEventFactory logEventFactory); +} 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..eec1dbe05b2 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 @@ -1,24 +1,34 @@ /* * 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.logging.log4j.core.async; +import com.lmax.disruptor.EventFactory; +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.EventTranslatorTwoArg; +import com.lmax.disruptor.ExceptionHandler; +import com.lmax.disruptor.RingBuffer; +import com.lmax.disruptor.Sequence; +import com.lmax.disruptor.SequenceReportingEventHandler; +import com.lmax.disruptor.TimeoutException; +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.dsl.Disruptor; +import com.lmax.disruptor.dsl.ProducerType; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.LogEvent; @@ -27,21 +37,11 @@ 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; -import com.lmax.disruptor.EventFactory; -import com.lmax.disruptor.EventTranslatorTwoArg; -import com.lmax.disruptor.ExceptionHandler; -import com.lmax.disruptor.RingBuffer; -import com.lmax.disruptor.Sequence; -import com.lmax.disruptor.SequenceReportingEventHandler; -import com.lmax.disruptor.TimeoutException; -import com.lmax.disruptor.WaitStrategy; -import com.lmax.disruptor.dsl.Disruptor; -import com.lmax.disruptor.dsl.ProducerType; - /** * Helper class decoupling the {@code AsyncLoggerConfig} class from the LMAX Disruptor library. *

@@ -63,8 +63,7 @@ public class AsyncLoggerConfigDisruptor extends AbstractLifeCycle implements Asy * RingBuffer events contain all information necessary to perform the work in a separate thread. */ public static class Log4jEventWrapper { - public Log4jEventWrapper() { - } + public Log4jEventWrapper() {} public Log4jEventWrapper(final MutableLogEvent mutableLogEvent) { event = mutableLogEvent; @@ -93,13 +92,18 @@ public String toString() { /** * EventHandler performs the work in a separate thread. + *

+ * Warning: this implementation only works with Disruptor 4.x. + *

*/ - private static class Log4jEventWrapperHandler implements SequenceReportingEventHandler { + private static class Log4jEventWrapperHandler implements EventHandler { private static final int NOTIFY_PROGRESS_THRESHOLD = 50; private Sequence sequenceCallback; private int counter; - @Override + /* + * Overrides a method from Disruptor 4.x. Do not remove. + */ public void setSequenceCallback(final Sequence sequenceCallback) { this.sequenceCallback = sequenceCallback; } @@ -108,7 +112,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); @@ -126,55 +130,61 @@ private void notifyIntermediateProgress(final long sequence) { } } + /** + * EventHandler performs the work in a separate thread. + *

+ * Warning: this implementation only works with Disruptor 3.x. + *

+ */ + private static final class Log4jEventWrapperHandler3 extends Log4jEventWrapperHandler + implements SequenceReportingEventHandler { + public Log4jEventWrapperHandler3() {} + } + /** * 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() { + (ringBufferElement, sequence, logEvent, loggerConfig) -> { + ((MutableLogEvent) ringBufferElement.event).initFrom(logEvent); + ringBufferElement.loggerConfig = loggerConfig; + }; - @Override - public void translateTo(final Log4jEventWrapper ringBufferElement, final long sequence, - final LogEvent logEvent, final AsyncLoggerConfig loggerConfig) { - ((MutableLogEvent) ringBufferElement.event).initFrom(logEvent); - ringBufferElement.loggerConfig = loggerConfig; + private Log4jEventWrapperHandler createEventHandler() { + if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 3) { + try { + return (Log4jEventWrapperHandler) Class.forName( + "org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor$Log4jEventWrapperHandler3") + .getConstructor() + .newInstance(); + } catch (final ReflectiveOperationException | LinkageError e) { + LOGGER.warn("Failed to create event handler for LMAX Disruptor 3.x, trying version 4.x.", e); + } } - }; + return new Log4jEventWrapperHandler(); + } private int ringBufferSize; private AsyncQueueFullPolicy asyncQueueFullPolicy; @@ -184,9 +194,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() { + public AsyncLoggerConfigDisruptor(final AsyncWaitStrategyFactory asyncWaitStrategyFactory) { + this.asyncWaitStrategyFactory = asyncWaitStrategyFactory; // may be null + } + + // package-protected for testing + WaitStrategy getWaitStrategy() { + return waitStrategy; } // called from AsyncLoggerConfig constructor @@ -212,9 +232,9 @@ public synchronized void start() { } LOGGER.trace("AsyncLoggerConfigDisruptor creating new disruptor for this configuration."); ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLoggerConfig.RingBufferSize"); - final WaitStrategy waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLoggerConfig.WaitStrategy"); + waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLoggerConfig.WaitStrategy", 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); @@ -231,12 +251,15 @@ public Thread newThread(final Runnable r) { final ExceptionHandler errorHandler = DisruptorUtil.getAsyncLoggerConfigExceptionHandler(); disruptor.setDefaultExceptionHandler(errorHandler); - final Log4jEventWrapperHandler[] handlers = {new Log4jEventWrapperHandler()}; + final Log4jEventWrapperHandler[] handlers = {createEventHandler()}; disruptor.handleEventsWith(handlers); - LOGGER.debug("Starting AsyncLoggerConfig disruptor for this configuration with ringbufferSize={}, " - + "waitStrategy={}, exceptionHandler={}...", disruptor.getRingBuffer().getBufferSize(), waitStrategy - .getClass().getSimpleName(), errorHandler); + LOGGER.debug( + "Starting AsyncLoggerConfig disruptor for this configuration with ringbufferSize={}, " + + "waitStrategy={}, exceptionHandler={}...", + disruptor.getRingBuffer().getBufferSize(), + waitStrategy.getClass().getSimpleName(), + errorHandler); disruptor.start(); super.start(); } @@ -277,7 +300,9 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { LOGGER.trace("AsyncLoggerConfigDisruptor: disruptor has been shut down."); if (DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy) > 0) { - LOGGER.trace("AsyncLoggerConfigDisruptor: {} discarded {} events.", asyncQueueFullPolicy, + LOGGER.trace( + "AsyncLoggerConfigDisruptor: {} discarded {} events.", + asyncQueueFullPolicy, DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy)); } setStopped(); @@ -329,8 +354,11 @@ public void enqueueEvent(final LogEvent event, final AsyncLoggerConfig asyncLogg } catch (final NullPointerException npe) { // Note: NPE prevents us from adding a log event to the disruptor after it was shut down, // which could cause the publishEvent method to hang and never return. - LOGGER.warn("Ignoring log event after log4j was shut down: {} [{}] {}", event.getLevel(), - event.getLoggerName(), event.getMessage().getFormattedMessage() + LOGGER.warn( + "Ignoring log event after log4j was shut down: {} [{}] {}", + event.getLevel(), + event.getLoggerName(), + event.getMessage().getFormattedMessage() + (event.getThrown() == null ? "" : Throwables.toStringList(event.getThrown()))); } } @@ -339,18 +367,20 @@ private LogEvent prepareEvent(final LogEvent event) { LogEvent logEvent = ensureImmutable(event); if (logEvent.getMessage() instanceof ReusableMessage) { if (logEvent instanceof Log4jLogEvent) { - ((Log4jLogEvent) logEvent).makeMessageImmutable(); + logEvent = logEvent.toImmutable(); } else if (logEvent instanceof MutableLogEvent) { // MutableLogEvents need to be translated into the RingBuffer by the MUTABLE_TRANSLATOR. - // That translator calls MutableLogEvent.initFrom to copy the event, which will makeMessageImmutable the message. + // That translator calls MutableLogEvent.initFrom to copy the event, which will makeMessageImmutable the + // message. if (translator != MUTABLE_TRANSLATOR) { // should not happen... // TRANSLATOR expects an immutable LogEvent - logEvent = ((MutableLogEvent) logEvent).createMemento(); + logEvent = logEvent.toImmutable(); } } else { // custom log event, with a ReusableMessage showWarningAboutCustomLogEventWithReusableMessage(logEvent); } - } else { // message is not a ReusableMessage; makeMessageImmutable it to prevent ConcurrentModificationExceptions + } else { // message is not a ReusableMessage; makeMessageImmutable it to prevent + // ConcurrentModificationExceptions InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage()); // LOG4J2-1988, LOG4J2-1914 } return logEvent; @@ -358,18 +388,37 @@ private LogEvent prepareEvent(final LogEvent event) { private void showWarningAboutCustomLogEventWithReusableMessage(final LogEvent logEvent) { if (!alreadyLoggedWarning) { - LOGGER.warn("Custom log event of type {} contains a mutable message of type {}." + - " AsyncLoggerConfig does not know how to make an immutable copy of this message." + - " This may result in ConcurrentModificationExceptions or incorrect log messages" + - " if the application modifies objects in the message while" + - " the background thread is writing it to the appenders.", - logEvent.getClass().getName(), logEvent.getMessage().getClass().getName()); + LOGGER.warn( + "Custom log event of type {} contains a mutable message of type {}." + + " AsyncLoggerConfig does not know how to make an immutable copy of this message." + + " This may result in ConcurrentModificationExceptions or incorrect log messages" + + " if the application modifies objects in the message while" + + " the background thread is writing it to the appenders.", + logEvent.getClass().getName(), + logEvent.getMessage().getClass().getName()); alreadyLoggedWarning = true; } } 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 @@ -387,7 +436,7 @@ private LogEvent ensureImmutable(final LogEvent event) { // The original event will be re-used and modified in an application thread later, // so take a snapshot of it, which can be safely processed in the // some-loggers-async background thread. - result = ((RingBufferLogEvent) event).createMemento(); + result = event.toImmutable(); } return result; } 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..74df6579e53 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 @@ -1,24 +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. */ 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; @@ -36,29 +35,33 @@ 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 String configLocn) { super(name, externalContext, configLocn); - loggerDisruptor = new AsyncLoggerDisruptor(name); + 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 +70,7 @@ public void setName(final String name) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.LoggerContext#start() */ @Override @@ -78,7 +81,7 @@ public void start() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.LoggerContext#start(org.apache.logging.log4j.core.config.Configuration) */ @Override @@ -102,7 +105,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..856060728e9 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.async; import java.net.URI; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; import org.apache.logging.log4j.core.util.Constants; @@ -33,12 +32,13 @@ 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() { - return AsyncLoggerContextSelector.class.getName().equals( - PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR)); + return AsyncLoggerContextSelector.class + .getName() + .equals(PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR)); } @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..058d306c223 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 @@ -1,52 +1,22 @@ /* * 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.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..122b7af60b9 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 @@ -1,37 +1,41 @@ /* * 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.logging.log4j.core.async; +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.EventTranslatorVararg; +import com.lmax.disruptor.ExceptionHandler; +import com.lmax.disruptor.RingBuffer; +import com.lmax.disruptor.TimeoutException; +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.dsl.Disruptor; +import com.lmax.disruptor.dsl.ProducerType; +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.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 com.lmax.disruptor.ExceptionHandler; -import com.lmax.disruptor.RingBuffer; -import com.lmax.disruptor.TimeoutException; -import com.lmax.disruptor.WaitStrategy; -import com.lmax.disruptor.dsl.Disruptor; -import com.lmax.disruptor.dsl.ProducerType; +import org.apache.logging.log4j.message.Message; /** * Helper class for async loggers: AsyncLoggerDisruptor handles the mechanics of working with the LMAX Disruptor, and @@ -43,16 +47,46 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle { private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50; private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200; + /** + * Creates an appropriate event handler for the Disruptor library used. + */ + private static EventHandler createEventHandler() { + if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 3) { + try { + return (EventHandler) + // Avoid using `LoaderUtil`, which might choose an incorrect class loader – see #2768. + Class.forName("org.apache.logging.log4j.core.async.RingBufferLogEventHandler") + .getConstructor() + .newInstance(); + } catch (final ReflectiveOperationException | LinkageError e) { + LOGGER.warn("Failed to create event handler for LMAX Disruptor 3.x, trying version 4.x.", e); + } + } + return new RingBufferLogEventHandler4(); + } + + 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,36 +114,49 @@ 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"); - - final ThreadFactory threadFactory = new Log4jThreadFactory("AsyncLogger[" + contextName + "]", true, Thread.NORM_PRIORITY) { - @Override - public Thread newThread(final Runnable r) { - final Thread result = super.newThread(r); - backgroundThreadId = result.getId(); - return result; - } - }; + final AsyncWaitStrategyFactory factory = waitStrategyFactorySupplier.get(); // get factory from configuration + waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLogger.WaitStrategy", factory); + + final ThreadFactory threadFactory = + new Log4jThreadFactory("AsyncLogger[" + contextName + "]", true, Thread.NORM_PRIORITY) { + @Override + public Thread newThread(final Runnable r) { + final Thread result = super.newThread(r); + backgroundThreadId = result.getId(); + return result; + } + }; asyncQueueFullPolicy = AsyncQueueFullPolicyFactory.create(); - disruptor = new Disruptor<>(RingBufferLogEvent.FACTORY, ringBufferSize, threadFactory, ProducerType.MULTI, - waitStrategy); + disruptor = new Disruptor<>( + RingBufferLogEvent.FACTORY, ringBufferSize, threadFactory, ProducerType.MULTI, waitStrategy); final ExceptionHandler errorHandler = DisruptorUtil.getAsyncLoggerExceptionHandler(); disruptor.setDefaultExceptionHandler(errorHandler); - final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()}; - disruptor.handleEventsWith(handlers); + final EventHandler handler = createEventHandler(); + disruptor.handleEventsWith(handler); - LOGGER.debug("[{}] Starting AsyncLogger disruptor for this context with ringbufferSize={}, waitStrategy={}, " - + "exceptionHandler={}...", contextName, disruptor.getRingBuffer().getBufferSize(), waitStrategy - .getClass().getSimpleName(), errorHandler); + LOGGER.debug( + "[{}] Starting AsyncLogger disruptor for this context with ringbufferSize={}, waitStrategy={}, " + + "exceptionHandler={}...", + contextName, + disruptor.getRingBuffer().getBufferSize(), + waitStrategy.getClass().getSimpleName(), + errorHandler); disruptor.start(); - LOGGER.trace("[{}] AsyncLoggers use a {} translator", contextName, useThreadLocalTranslator ? "threadlocal" - : "vararg"); + LOGGER.trace( + "[{}] AsyncLoggers use a {} translator", + contextName, + useThreadLocalTranslator ? "threadlocal" : "vararg"); super.start(); } @@ -150,7 +197,9 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { LOGGER.trace("[{}] AsyncLoggerDisruptor: disruptor has been shut down.", contextName); if (DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy) > 0) { - LOGGER.trace("AsyncLoggerDisruptor: {} discarded {} events.", asyncQueueFullPolicy, + LOGGER.trace( + "AsyncLoggerDisruptor: {} discarded {} events.", + asyncQueueFullPolicy, DiscardingAsyncQueueFullPolicy.getDiscardCount(asyncQueueFullPolicy)); } setStopped(); @@ -191,9 +240,9 @@ private int remainingDisruptorCapacity() { } return (int) temp.getRingBuffer().remainingCapacity(); } - /** - * Returns {@code true} if the specified disruptor is null. - */ + /** + * Returns {@code true} if the specified disruptor is null. + */ private boolean hasLog4jBeenShutDown(final Disruptor aDisruptor) { if (aDisruptor == null) { // LOG4J2-639 LOGGER.warn("Ignoring log event after log4j was shut down"); @@ -202,32 +251,110 @@ 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. * @@ -252,7 +379,9 @@ public boolean isUseThreadLocals() { */ public void setUseThreadLocals(final boolean allow) { useThreadLocalTranslator = allow; - LOGGER.trace("[{}] AsyncLoggers have been modified to use a {} translator", contextName, + LOGGER.trace( + "[{}] AsyncLoggers have been modified to use a {} translator", + contextName, useThreadLocalTranslator ? "threadlocal" : "vararg"); } } 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..8064e49157c 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 @@ -1,45 +1,40 @@ /* * 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.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/AsyncQueueFullPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.java index 196d52d061e..d0d480854c6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; 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..c8c0fd44d46 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; @@ -66,27 +66,38 @@ public class AsyncQueueFullPolicyFactory { */ 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)) { + 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); LOGGER.debug("Creating custom AsyncQueueFullPolicy '{}'", router); - return cls.newInstance(); + return LoaderUtil.newCheckedInstanceOf(router, AsyncQueueFullPolicy.class); } catch (final Exception ex) { - LOGGER.debug("Using DefaultAsyncQueueFullPolicy. Could not create custom AsyncQueueFullPolicy '{}': {}", router, - ex.toString()); + LOGGER.debug( + "Using DefaultAsyncQueueFullPolicy. Could not create custom AsyncQueueFullPolicy '{}': {}", + router, + ex.getMessage(), + ex); return new DefaultAsyncQueueFullPolicy(); } } 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..7b5f1c0b1a6 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactory.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.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..3785de5a671 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfig.java @@ -0,0 +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.async; + +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.Assert; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * 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. + */ +@Plugin(name = "AsyncWaitStrategyFactory", category = Core.CATEGORY_NAME, printObject = true) +public class AsyncWaitStrategyFactoryConfig { + + /** + * Status logger for internal logging. + */ + protected static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + private final String factoryClassName; + + public AsyncWaitStrategyFactoryConfig(final String factoryClassName) { + this.factoryClassName = Assert.requireNonEmpty(factoryClassName, "factoryClassName"); + } + + @PluginBuilderFactory + 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; + } + + /** + * @since 2.26.0 + */ + public B setFactoryClassName(final String className) { + this.factoryClassName = + Assert.requireNonEmpty(className, "The 'className' argument must not be null or empty."); + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setFactoryClassName(String)}. + */ + @Deprecated + public B withFactoryClassName(final String className) { + this.factoryClassName = + Assert.requireNonEmpty(className, "The 'className' argument must not be null or empty."); + return asBuilder(); + } + + @Override + public AsyncWaitStrategyFactoryConfig build() { + if (!isValid()) { + return null; + } + return new AsyncWaitStrategyFactoryConfig(factoryClassName); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; + } + } + + public AsyncWaitStrategyFactory createWaitStrategyFactory() { + try { + return LoaderUtil.newCheckedInstanceOf(factoryClassName, AsyncWaitStrategyFactory.class); + } catch (final ClassCastException e) { + LOGGER.error("Ignoring factory '{}': it is not assignable to AsyncWaitStrategyFactory", factoryClassName); + return null; + } catch (ReflectiveOperationException e) { + LOGGER.info( + "Invalid implementation class name value: error creating AsyncWaitStrategyFactory {}: {}", + factoryClassName, + e.getMessage(), + 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..d8c5369c3b4 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.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.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.ContextAnchor; +import org.apache.logging.log4j.core.selector.ContextSelector; + +/** + * 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. + */ +public class BasicAsyncLoggerContextSelector implements ContextSelector { + + private static final AsyncLoggerContext CONTEXT = new AsyncLoggerContext("AsyncDefault"); + + @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 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) { + 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; + } + + @Override + public void removeContext(final LoggerContext context) { + // does not remove anything + } + + @Override + public boolean isClassLoaderDependent() { + return false; + } + + @Override + public List getLoggerContexts() { + return Collections.singletonList(CONTEXT); + } +} 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..6cccd799766 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; 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..52a8b4d199a 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 @@ -1,22 +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. */ 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..eda242feb4d --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncWaitStrategyFactory.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.async; + +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; +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; + +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(final 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); + // Check for the v 4.x version of the strategy, the version in 3.x is not garbage-free. + if (DisruptorUtil.DISRUPTOR_MAJOR_VERSION == 4) { + try { + return (WaitStrategy) Class.forName("com.lmax.disruptor.TimeoutBlockingWaitStrategy") + .getConstructor(long.class, TimeUnit.class) + .newInstance(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (final ReflectiveOperationException | LinkageError e) { + LOGGER.debug( + "DefaultAsyncWaitStrategyFactory failed to load 'com.lmax.disruptor.TimeoutBlockingWaitStrategy', using '{}' instead.", + TimeoutBlockingWaitStrategy.class.getName()); + } + } + // Use our version + 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, final 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/DiscardingAsyncQueueFullPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicy.java index dfac633375f..b46d9cd0cff 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicy.java @@ -1,33 +1,32 @@ /* * 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.logging.log4j.core.async; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - /** * Discarding router extends the DefaultAsyncQueueFullPolicy by first verifying if the queue is fuller than the specified * threshold ratio; if this is the case, log events {@linkplain Level#isMoreSpecificThan(Level) more specific} than * the specified threshold level are dropped. If this is not the case, the {@linkplain DefaultAsyncQueueFullPolicy - * default routing rules hold. + * default routing} rules hold. */ public class DiscardingAsyncQueueFullPolicy extends DefaultAsyncQueueFullPolicy { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -49,10 +48,12 @@ public DiscardingAsyncQueueFullPolicy(final Level thresholdLevel) { public EventRoute getRoute(final long backgroundThreadId, final Level level) { if (level.isLessSpecificThan(thresholdLevel)) { if (discardCount.getAndIncrement() == 0) { - LOGGER.warn("Async queue is full, discarding event with level {}. " + - "This message will only appear once; future events from {} " + - "are silently discarded until queue capacity becomes available.", - level, thresholdLevel); + LOGGER.warn( + "Async queue is full, discarding event with level {}. " + + "This message will only appear once; future events from {} " + + "are silently discarded until queue capacity becomes available.", + level, + thresholdLevel); } return EventRoute.DISCARD; } 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..d20dfb9a105 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 @@ -1,25 +1,24 @@ /* * 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.logging.log4j.core.async; -import java.util.concurrent.BlockingQueue; - import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import com.conversantmedia.util.concurrent.SpinPolicy; +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.PluginAttribute; @@ -46,8 +45,7 @@ public BlockingQueue create(final int capacity) { @PluginFactory public static DisruptorBlockingQueueFactory createFactory( - @PluginAttribute(value = "SpinPolicy", defaultString = "WAITING") final SpinPolicy spinPolicy - ) { + @PluginAttribute(value = "SpinPolicy", 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..f115e2ae378 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 @@ -1,29 +1,25 @@ /* * 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.logging.log4j.core.async; -import java.util.Locale; -import java.util.concurrent.Callable; +import com.lmax.disruptor.ExceptionHandler; +import com.lmax.disruptor.WaitStrategy; 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.util.Integers; @@ -39,52 +35,61 @@ final class DisruptorUtil { private static final int RINGBUFFER_MIN_SIZE = 128; private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024; private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024; + public static final String LOGGER_EXCEPTION_HANDLER_PROPERTY = "AsyncLogger.ExceptionHandler"; + public static final String LOGGER_CONFIG_EXCEPTION_HANDLER_PROPERTY = "AsyncLoggerConfig.ExceptionHandler"; - 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("AsyncLogger.SynchronizeEnqueueWhenQueueFull", true); - static long getTimeout(final String propertyName, final long defaultTimeout) { - return PropertiesUtil.getProperties().getLongProperty(propertyName, defaultTimeout); - } + static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() + .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true); + + static final int DISRUPTOR_MAJOR_VERSION = detectDisruptorMajorVersion(); - 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); + // TODO: replace with LoaderUtil.isClassAvailable() when TCCL is removed + // See: https://github.com/apache/logging-log4j2/issues/3706 + private static int detectDisruptorMajorVersion() { + try { + Class.forName( + "com.lmax.disruptor.SequenceReportingEventHandler", true, DisruptorUtil.class.getClassLoader()); + return 3; + } catch (final ClassNotFoundException e) { + return 4; + } } - 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); + private DisruptorUtil() {} + + static WaitStrategy createWaitStrategy( + final String propertyName, final AsyncWaitStrategyFactory asyncWaitStrategyFactory) { + + 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; - final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName, - String.valueOf(ringBufferSize)); + final String userPreferredRBSize = + PropertiesUtil.getProperties().getStringProperty(propertyName, String.valueOf(ringBufferSize)); try { - int size = Integer.parseInt(userPreferredRBSize); + int size = Integers.parseInt(userPreferredRBSize); if (size < RINGBUFFER_MIN_SIZE) { size = RINGBUFFER_MIN_SIZE; - LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize, - RINGBUFFER_MIN_SIZE); + LOGGER.warn( + "Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize, RINGBUFFER_MIN_SIZE); } ringBufferSize = size; } catch (final Exception ex) { @@ -94,33 +99,23 @@ static int calculateRingBufferSize(final String propertyName) { } static ExceptionHandler getAsyncLoggerExceptionHandler() { - final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler"); - if (cls == null) { - return new AsyncLoggerDefaultExceptionHandler(); - } try { - @SuppressWarnings("unchecked") - final Class> klass = - (Class>) LoaderUtil.loadClass(cls); - return klass.newInstance(); - } catch (final Exception ignored) { - LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: error creating {}: ", cls, ignored); + return LoaderUtil.newCheckedInstanceOfProperty( + LOGGER_EXCEPTION_HANDLER_PROPERTY, ExceptionHandler.class, AsyncLoggerDefaultExceptionHandler::new); + } catch (final ReflectiveOperationException e) { + LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: {}", e.getMessage(), e); return new AsyncLoggerDefaultExceptionHandler(); } } static ExceptionHandler getAsyncLoggerConfigExceptionHandler() { - final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLoggerConfig.ExceptionHandler"); - if (cls == null) { - return new AsyncLoggerConfigDefaultExceptionHandler(); - } try { - @SuppressWarnings("unchecked") - final Class> klass = - (Class>) LoaderUtil.loadClass(cls); - return klass.newInstance(); - } catch (final Exception ignored) { - LOGGER.debug("Invalid AsyncLoggerConfig.ExceptionHandler value: error creating {}: ", cls, ignored); + return LoaderUtil.newCheckedInstanceOfProperty( + LOGGER_CONFIG_EXCEPTION_HANDLER_PROPERTY, + ExceptionHandler.class, + AsyncLoggerConfigDefaultExceptionHandler::new); + } catch (final ReflectiveOperationException e) { + LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: {}", e.getMessage(), e); return new AsyncLoggerConfigDefaultExceptionHandler(); } } @@ -133,17 +128,12 @@ 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) { - final String msg = "Could not obtain executor thread Id. " - + "Giving up to avoid the risk of application deadlock."; + final String msg = + "Could not obtain executor thread Id. " + "Giving up to avoid the risk of application deadlock."; throw new IllegalStateException(msg, 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..2c6d50d4e99 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; @@ -37,13 +37,17 @@ public enum EventRoute { */ ENQUEUE { @Override - public void logMessage(final AsyncLogger asyncLogger, final String fqcn, final Level level, - final Marker marker, final Message message, final Throwable thrown) { - } + public void logMessage( + final AsyncLogger asyncLogger, + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable thrown) {} @Override public void logMessage(final AsyncLoggerConfig asyncLoggerConfig, final LogEvent event) { - asyncLoggerConfig.callAppendersInBackgroundThread(event); + asyncLoggerConfig.logInBackgroundThread(event); } @Override @@ -53,16 +57,22 @@ 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 - public void logMessage(final AsyncLogger asyncLogger, final String fqcn, final Level level, - final Marker marker, final Message message, final Throwable thrown) { - } + public void logMessage( + final AsyncLogger asyncLogger, + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable thrown) {} @Override public void logMessage(final AsyncLoggerConfig asyncLoggerConfig, final LogEvent event) { - asyncLoggerConfig.callAppendersInCurrentThread(event); + asyncLoggerConfig.logToAsyncLoggerConfigsOnCurrentThread(event); } @Override @@ -75,8 +85,13 @@ public void logMessage(final AsyncAppender asyncAppender, final LogEvent logEven */ DISCARD { @Override - public void logMessage(final AsyncLogger asyncLogger, final String fqcn, final Level level, - final Marker marker, final Message message, final Throwable thrown) { + public void logMessage( + final AsyncLogger asyncLogger, + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable thrown) { // do nothing: drop the event } @@ -91,8 +106,13 @@ public void logMessage(final AsyncAppender asyncAppender, final LogEvent coreEve } }; - public abstract void logMessage(final AsyncLogger asyncLogger, final String fqcn, final Level level, - final Marker marker, final Message message, final Throwable thrown); + public abstract void logMessage( + final AsyncLogger asyncLogger, + final String fqcn, + final Level level, + final Marker marker, + final Message message, + final Throwable thrown); public abstract void logMessage(final AsyncLoggerConfig asyncLoggerConfig, final LogEvent event); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java index 4959733b706..aa82a0dfe3e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/InternalAsyncUtil.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; 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..000a13b8bab 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.async; @@ -20,7 +20,6 @@ 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; @@ -48,7 +47,7 @@ public BlockingQueue create(final int capacity) { @PluginFactory public static JCToolsBlockingQueueFactory createFactory( - @PluginAttribute(value = "WaitStrategy", defaultString = "PARK") final WaitStrategy waitStrategy) { + @PluginAttribute(value = "WaitStrategy", defaultString = "PARK") final WaitStrategy waitStrategy) { return new JCToolsBlockingQueueFactory<>(waitStrategy); } @@ -71,12 +70,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(e -> c.add(e), maxElements); } @Override @@ -90,7 +84,7 @@ public boolean offer(final E e, final long timeout, final TimeUnit unit) throws return false; } idleCounter = waitStrategy.idle(idleCounter); - } while (!Thread.interrupted()); //clear interrupted flag + } while (!Thread.interrupted()); // clear interrupted flag throw new InterruptedException(); } @@ -106,7 +100,7 @@ public E poll(final long timeout, final TimeUnit unit) throws InterruptedExcepti return null; } idleCounter = waitStrategy.idle(idleCounter); - } while (!Thread.interrupted()); //clear interrupted flag + } while (!Thread.interrupted()); // clear interrupted flag throw new InterruptedException(); } @@ -118,13 +112,13 @@ public void put(final E e) throws InterruptedException { return; } idleCounter = waitStrategy.idle(idleCounter); - } while (!Thread.interrupted()); //clear interrupted flag + } while (!Thread.interrupted()); // clear interrupted flag throw new InterruptedException(); } @Override public boolean offer(final E e) { - //keep 2 cache lines empty to avoid false sharing that will slow the consumer thread when queue is full. + // keep 2 cache lines empty to avoid false sharing that will slow the consumer thread when queue is full. return offerIfBelowThreshold(e, capacity() - 32); } @@ -142,42 +136,28 @@ public E take() throws InterruptedException { return result; } idleCounter = waitStrategy.idle(idleCounter); - } while (!Thread.interrupted()); //clear interrupted flag + } while (!Thread.interrupted()); // clear interrupted flag throw new 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; @@ -194,5 +174,4 @@ private int idle(final int idleCounter) { private interface Idle { int idle(int idleCounter); } - } 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..2b5176f0d46 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 @@ -1,25 +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. */ - package org.apache.logging.log4j.core.async; 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; 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..71d1b55f5d4 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 @@ -1,37 +1,43 @@ /* * 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.logging.log4j.core.async; +import com.lmax.disruptor.EventFactory; import java.io.IOException; +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.ContextStack; 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.Instant; import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.Clock; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.NanoClock; 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; @@ -40,13 +46,11 @@ import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.Strings; -import com.lmax.disruptor.EventFactory; - /** * 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 +65,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,10 +81,10 @@ 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; - private ThrowableProxy thrownProxy; private StringMap contextData = ContextDataFactory.createContextData(); private Marker marker; private String fqcn; @@ -93,11 +93,22 @@ public RingBufferLogEvent newInstance() { private transient AsyncLogger asyncLogger; - public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker, - final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable, - final StringMap mutableContextData, final ContextStack aContextStack, final long threadId, - final String threadName, final int threadPriority, final StackTraceElement aLocation, - final Clock clock, final NanoClock nanoClock) { + public void setValues( + final AsyncLogger anAsyncLogger, + final String aLoggerName, + final Marker aMarker, + final String theFqcn, + final Level aLevel, + final Message msg, + final Throwable aThrowable, + final StringMap mutableContextData, + final ContextStack aContextStack, + final long threadId, + final String threadName, + final int threadPriority, + final StackTraceElement aLocation, + final Clock clock, + final NanoClock nanoClock) { this.threadPriority = threadPriority; this.threadId = threadId; this.level = aLevel; @@ -107,13 +118,13 @@ public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, initTime(clock); this.nanoTime = nanoClock.nanoTime(); this.thrown = aThrowable; - this.thrownProxy = null; this.marker = aMarker; this.fqcn = theFqcn; this.location = aLocation; this.contextData = mutableContextData; this.contextStack = aContextStack; this.asyncLogger = anAsyncLogger; + this.populated = true; } private void initTime(final Clock clock) { @@ -126,17 +137,16 @@ private void initTime(final Clock clock) { @Override public LogEvent toImmutable() { - return createMemento(); + return Log4jLogEvent.createMemento(this); } 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 +154,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 +172,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 +250,7 @@ public String getFormattedMessage() { */ @Override public String getFormat() { - return null; + return messageFormat; } /** @@ -281,13 +298,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,34 +332,16 @@ 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 - if (thrown == null) { - if (thrownProxy != null) { - thrown = thrownProxy.getThrowable(); - } - } return thrown; } @Override public ThrowableProxy getThrownProxy() { - // lazily instantiate the (expensive) ThrowableProxy - if (thrownProxy == null) { - if (thrown != null) { - thrownProxy = new ThrowableProxy(thrown); - } - } - return this.thrownProxy; + return thrown != null ? new ThrowableProxy(thrown) : null; } - @SuppressWarnings("unchecked") @Override public ReadOnlyStringMap getContextData() { return contextData; @@ -344,7 +351,6 @@ void setContextData(final StringMap contextData) { this.contextData = contextData; } - @SuppressWarnings("unchecked") @Override public Map getContextMap() { return contextData.toMap(); @@ -377,7 +383,9 @@ public StackTraceElement getSource() { @Override public long getTimeMillis() { - return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : instant.getEpochMillisecond(); + return message instanceof TimestampMessage + ? ((TimestampMessage) message).getTimestamp() + : instant.getEpochMillisecond(); } @Override @@ -394,16 +402,40 @@ public long getNanoTime() { * Release references held by ring buffer to allow objects to be garbage-collected. */ public void clear() { - this.asyncLogger = null; + this.populated = false; + this.level = null; + this.threadName = null; this.loggerName = null; + clearMessage(); + this.thrown = null; + clearContextData(); this.marker = null; this.fqcn = null; - this.level = null; - this.message = null; - this.thrown = null; - this.thrownProxy = null; - this.contextStack = null; this.location = null; + this.contextStack = null; + this.asyncLogger = null; + } + + private void clearMessage() { + message = null; + messageFormat = null; + // ensure that excessively long char[] arrays are not kept in memory forever + if (Constants.ENABLE_THREADLOCALS) { + StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); + + 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; + } + } + + private void clearContextData() { if (contextData != null) { if (contextData.isFrozen()) { // came from CopyOnWrite thread context contextData = null; @@ -411,37 +443,44 @@ public void clear() { contextData.clear(); } } + } - // ensure that excessively long char[] arrays are not kept in memory forever - StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); - - if (parameters != null) { - for (int i = 0; i < parameters.length; i++) { - parameters[i] = null; - } - } + private Object writeReplace() throws IOException { + return Log4jLogEvent.serialize(this, this.includeLocation); } - private void writeObject(final java.io.ObjectOutputStream out) throws IOException { - getThrownProxy(); // initialize the ThrowableProxy before serializing - out.defaultWriteObject(); + private void readObject(final ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required"); } /** * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}. * * @return a new immutable copy of the data in this {@code RingBufferLogEvent} + * @deprecated since 2.25.0. Use {@link LogEvent#toImmutable()} instead. */ + @Deprecated public LogEvent createMemento() { - return new Log4jLogEvent.Builder(this).build(); - + return toImmutable(); } /** * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}. * @param builder the builder whose fields to populate + * + * @deprecated since 2.25.0. Use {@link Log4jLogEvent.Builder#Builder(LogEvent)} instead. */ + @Deprecated public void initializeBuilder(final Log4jLogEvent.Builder builder) { + // If the data is not frozen, make a copy of it. + final StringMap oldContextData = this.contextData; + final StringMap contextData; + if (oldContextData != null && !oldContextData.isFrozen()) { + contextData = ContextDataFactory.createContextData(); + contextData.putAll(oldContextData); + } else { + contextData = oldContextData; + } builder.setContextData(contextData) // .setContextStack(contextStack) // .setEndOfBatch(endOfBatch) // @@ -450,16 +489,14 @@ 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) // .setThreadName(threadName) // .setThreadPriority(threadPriority) // .setThrown(getThrown()) // may deserialize from thrownProxy - .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy .setInstant(instant) // ; } - } 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..a51f0a525ec 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.async; import com.lmax.disruptor.LifecycleAware; -import com.lmax.disruptor.Sequence; import com.lmax.disruptor.SequenceReportingEventHandler; /** @@ -25,50 +24,11 @@ * available. Processing of these messages is done in a separate thread, * controlled by the {@code Executor} passed to the {@code Disruptor} * constructor. + *

+ * Warning: this class only works with Disruptor 3.x. + *

+ * @deprecated Only used internally, will be removed in the next major version. */ -public class RingBufferLogEventHandler implements - SequenceReportingEventHandler, LifecycleAware { - - private static final int NOTIFY_PROGRESS_THRESHOLD = 50; - private Sequence sequenceCallback; - private int counter; - private long threadId = -1; - - @Override - public void setSequenceCallback(final Sequence sequenceCallback) { - this.sequenceCallback = sequenceCallback; - } - - @Override - public void onEvent(final RingBufferLogEvent event, final long sequence, - final boolean endOfBatch) throws Exception { - event.execute(endOfBatch); - 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. - if (++counter > NOTIFY_PROGRESS_THRESHOLD) { - sequenceCallback.set(sequence); - counter = 0; - } - } - - /** - * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started - * yet. - * @return the thread ID of the background consumer thread, or {@code -1} - */ - public long getThreadId() { - return threadId; - } - - @Override - public void onStart() { - threadId = Thread.currentThread().getId(); - } - - @Override - public void onShutdown() { - } -} +@Deprecated +public class RingBufferLogEventHandler extends RingBufferLogEventHandler4 + implements SequenceReportingEventHandler, LifecycleAware {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler4.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler4.java new file mode 100644 index 00000000000..6cb1e686126 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler4.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.async; + +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.Sequence; + +/** + * This event handler gets passed messages from the RingBuffer as they become + * available. Processing of these messages is done in a separate thread, + * controlled by the {@code Executor} passed to the {@code Disruptor} + * constructor. + * *

+ * * Warning: this class only works with Disruptor 4.x. + * *

+ */ +class RingBufferLogEventHandler4 implements EventHandler { + + private static final int NOTIFY_PROGRESS_THRESHOLD = 50; + private Sequence sequenceCallback; + private int counter; + private long threadId = -1; + + /* + * Overrides a method from Disruptor 4.x. Do not remove. + */ + public void setSequenceCallback(final Sequence sequenceCallback) { + this.sequenceCallback = sequenceCallback; + } + + @Override + public void onEvent(final RingBufferLogEvent event, final long sequence, final boolean endOfBatch) + throws Exception { + 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); + } + } + + private void notifyCallback(final long sequence) { + if (++counter > NOTIFY_PROGRESS_THRESHOLD) { + sequenceCallback.set(sequence); + counter = 0; + } + } + + /** + * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started + * yet. + * + * @return the thread ID of the background consumer thread, or {@code -1} + */ + public long getThreadId() { + return threadId; + } + + /* + * Overrides a method from Disruptor 4.x. Do not remove. + */ + public void onStart() { + threadId = Thread.currentThread().getId(); + } + + /* + * Overrides a method from Disruptor 4.x. Do not remove. + */ + public void onShutdown() {} +} 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..f6fee9fcf5a 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 @@ -1,21 +1,22 @@ /* * 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.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; @@ -23,21 +24,22 @@ 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.message.Message; - -import com.lmax.disruptor.EventTranslator; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.StringMap; /** * This class is responsible for writing elements that make up a log event into * the ringbuffer {@code RingBufferLogEvent}. After this translator populated * the ringbuffer event, the disruptor will update the sequence number so that * the event can be consumed by another thread. + *

+ * Usage note: This class is only used on the thread that created it. + *

*/ -public class RingBufferLogEventTranslator implements - EventTranslator { +public class RingBufferLogEventTranslator implements EventTranslator { - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); + private static final ContextDataInjector INJECTOR = ContextDataInjectorFactory.createInjector(); private AsyncLogger asyncLogger; String loggerName; protected Marker marker; @@ -46,31 +48,49 @@ public class RingBufferLogEventTranslator implements protected Message message; protected Throwable thrown; private ContextStack contextStack; - private long threadId = Thread.currentThread().getId(); - private String threadName = Thread.currentThread().getName(); - private int threadPriority = Thread.currentThread().getPriority(); private StackTraceElement location; private Clock clock; private NanoClock nanoClock; + // Due to the usage pattern of this class, these are effectively final + private long threadId = Thread.currentThread().getId(); + private String threadName = Thread.currentThread().getName(); + private int threadPriority = Thread.currentThread().getPriority(); + // @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 { + final ReadOnlyStringMap contextData = event.getContextData(); + 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, contextData instanceof StringMap ? (StringMap) contextData : null), + 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() { - setBasicValues(null, // asyncLogger + void clear() { + setBasicValues( + null, // asyncLogger null, // loggerName null, // marker null, // fqcn @@ -81,13 +101,21 @@ private void clear() { null, // location null, // clock null // nanoClock - ); + ); } - 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) { + 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) { this.asyncLogger = anAsyncLogger; this.loggerName = aLoggerName; this.marker = aMarker; @@ -101,6 +129,11 @@ public void setBasicValues(final AsyncLogger anAsyncLogger, final String aLogger this.nanoClock = aNanoClock; } + /** + * @deprecated since 2.25.0. {@link RingBufferLogEventTranslator} instances should only be used on the thread that + * created it. + */ + @Deprecated public void updateThreadValues() { final Thread currentThread = Thread.currentThread(); this.threadId = currentThread.getId(); 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..ec3b6509ff4 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,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 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.util.Integers; +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("AsyncLogger.ThreadNameStrategy"); + try { + final ThreadNameCachingStrategy result = + name != null ? ThreadNameCachingStrategy.valueOf(name) : DEFAULT_STRATEGY; + LOGGER.debug( + "AsyncLogger.ThreadNameStrategy={} (user specified {}, default is {})", + result.name(), + name, + DEFAULT_STRATEGY.name()); + return result; + } catch (final Exception ex) { + LOGGER.debug( + "Using AsyncLogger.ThreadNameStrategy.{}: '{}' not valid: {}", + 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 Integers.parseInt(m.group(3)) == 0 && Integers.parseInt(m.group(4)) < 102; + } + return true; + } catch (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..5adb0913c56 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/TimeoutBlockingWaitStrategy.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 { + final long millis = timeoutNanos / ONE_MILLISECOND_IN_NANOSECONDS; + final long nanos = timeoutNanos % ONE_MILLISECOND_IN_NANOSECONDS; + + final long t0 = System.nanoTime(); + mutex.wait(millis, (int) nanos); + final long t1 = System.nanoTime(); + + return timeoutNanos - (t1 - t0); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java index 68d069f60d7..24e9b7643f0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java @@ -17,4 +17,9 @@ /** * Provides Asynchronous Logger classes and interfaces for low-latency logging. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.async; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..402dfe6d7fd 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 @@ -1,26 +1,27 @@ /* * 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.logging.log4j.core.config; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.Serializable; import java.lang.ref.WeakReference; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -35,26 +36,30 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; - 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.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.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.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.filter.AbstractFilterable; -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.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; @@ -66,8 +71,14 @@ 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.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.Strings; /** * The base Configuration. Many configuration implementations will extend this class. @@ -104,7 +115,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement /** * Shutdown timeout in milliseconds. */ - protected long shutdownTimeoutMillis = 0; + protected long shutdownTimeoutMillis; /** * The Script manager. @@ -115,21 +126,25 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement * 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 final ConcurrentMap properties = new ConcurrentHashMap<>(); - private final StrLookup tempLookup = new Interpolator(properties); - private final StrSubstitutor subst = new StrSubstitutor(tempLookup); + private Set monitorResources = Collections.emptySet(); + private final ConcurrentMap propertyMap = new ConcurrentHashMap<>(); + private final Interpolator tempLookup = new Interpolator(propertyMap); + private final StrSubstitutor runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup); + private final StrSubstitutor configurationStrSubstitutor = new ConfigurationStrSubstitutor(runtimeStrSubstitutor); 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 AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor; + private AsyncWaitStrategyFactory asyncWaitStrategyFactory; private NanoClock nanoClock = new DummyNanoClock(); private final WeakReference loggerContext; @@ -138,14 +153,14 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement */ protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) { this.loggerContext = new WeakReference<>(loggerContext); + tempLookup.setLoggerContext(loggerContext); // 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"); - componentMap.put(Configuration.CONTEXT_PROPERTIES, properties); + componentMap.put(CONTEXT_PROPERTIES, propertyMap); pluginManager = new PluginManager(Node.CATEGORY); rootNode = new Node(); setState(State.INITIALIZING); - } @Override @@ -160,7 +175,7 @@ public List getPluginPackages() { @Override public Map getProperties() { - return properties; + return propertyMap; } @Override @@ -199,23 +214,37 @@ 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); - try { - scriptManager = new ScriptManager(this, watchManager); - } 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); + LOGGER.debug(Version.getProductString() + " initializing configuration {}", this); + runtimeStrSubstitutor.setConfiguration(this); + configurationStrSubstitutor.setConfiguration(this); + final String scriptLanguages = PropertiesUtil.getProperties().getStringProperty(Constants.SCRIPT_LANGUAGES); + if (scriptLanguages != null) { + try { + scriptManager = new ScriptManager(this, watchManager, scriptLanguages); + } 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); + } + } + if (!pluginPackages.isEmpty()) { + LOGGER.warn("The use of package scanning to locate Log4j plugins is deprecated.\n" + + "Please remove the `packages` attribute from your configuration file.\n" + + "See https://logging.apache.org/log4j/2.x/faq.html#package-scanning for details."); } pluginManager.collectPlugins(pluginPackages); final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); @@ -225,32 +254,82 @@ public void initialize() { 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()); + 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); + LOGGER.error( + "Unable to initialize {} due to {}", + type.getPluginClass().getName(), + e.getClass().getSimpleName(), + e); } } } setup(); setupAdvertisement(); doConfigure(); + watchMonitorResources(); setState(State.INITIALIZED); LOGGER.debug("Configuration {} initialized", this); } + 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); + File file = configSource.getFile(); + if (file != null) { + final Source cfgSource = new Source(file); + final long lastModified = file.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); + } + } + } + + private void monitorSource(final Reconfigurable reconfigurable, final ConfigurationSource configSource) { + URI uri = configSource.getURI(); + if (uri != null && configSource.getLastModified() > 0) { + File file = configSource.getFile(); + final Source cfgSource = file != null ? new Source(file) : new Source(uri); + final Watcher watcher = WatcherFactory.getInstance(pluginPackages) + .newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified()); + if (watcher != null) { + watchManager.watch(cfgSource, watcher); + } + } else { + LOGGER.info("{} does not support dynamic reconfiguration", configSource.getURI()); + } + } + /** * Start the configuration. */ @Override public void start() { // Preserve the prior behavior of initializing during start if not initialized. - if (getState().equals(State.INITIALIZING)) { + if (getState() == State.INITIALIZING) { initialize(); } - LOGGER.debug("Starting configuration {}", this); + LOGGER.info("Starting configuration {}...", this); this.setStarting(); - if (watchManager.getIntervalSeconds() > 0) { + if (isConfigurationMonitoringEnabled()) { + LOGGER.info( + "Start watching for changes to {} every {} seconds", + watchManager.getConfigurationWatchers().keySet(), + watchManager.getIntervalSeconds()); watchManager.start(); } if (hasAsyncLoggers()) { @@ -268,7 +347,22 @@ public void start() { root.start(); // LOG4J2-336 } super.start(); - LOGGER.debug("Started configuration {} OK.", this); + LOGGER.info("Configuration {} started.", this); + } + + private boolean isConfigurationMonitoringEnabled() { + return this instanceof Reconfigurable && watchManager.getIntervalSeconds() > 0; + } + + private void watchMonitorResources() { + if (isConfigurationMonitoringEnabled()) { + monitorResources.forEach(monitorResource -> { + Source source = new Source(monitorResource.getUri()); + final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher( + this, (Reconfigurable) this, listeners, source.getFile().lastModified()); + watchManager.watch(source, watcher); + }); + } } private boolean hasAsyncLoggers() { @@ -288,9 +382,9 @@ private boolean hasAsyncLoggers() { */ @Override public boolean stop(final long timeout, final TimeUnit timeUnit) { + LOGGER.info("Stopping configuration {}...", this); this.setStopping(); super.stop(timeout, timeUnit, false); - LOGGER.trace("Stopping {}...", this); // Stop the components that are closest to the application first: // 1. Notify all LoggerConfigs' ReliabilityStrategy that the configuration will be stopped. @@ -311,8 +405,8 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { root.getReliabilityStrategy().beforeStopConfiguration(this); final String cls = getClass().getSimpleName(); - LOGGER.trace("{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size() - + 1); + LOGGER.trace( + "{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size() + 1); if (!loggerConfigs.isEmpty()) { LOGGER.trace("{} stopping {} LoggerConfigs.", cls, loggerConfigs.size()); @@ -330,8 +424,14 @@ 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 @@ -345,12 +445,6 @@ public boolean stop(final long timeout, final TimeUnit 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) { @@ -386,7 +480,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { advertiser.unadvertise(advertisement); } setStopped(); - LOGGER.debug("Stopped {} OK", this); + LOGGER.info("Configuration {} stopped.", this); return true; } @@ -414,9 +508,13 @@ public void setup() { // default does nothing, subclasses do work. } + @SuppressWarnings("deprecation") protected Level getDefaultStatus() { - final String statusLevel = PropertiesUtil.getProperties().getStringProperty( - Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name()); + final PropertiesUtil properties = PropertiesUtil.getProperties(); + String statusLevel = properties.getStringProperty(StatusLogger.DEFAULT_STATUS_LISTENER_LEVEL); + if (statusLevel == null) { + statusLevel = properties.getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name()); + } try { return Level.toLevel(statusLevel); } catch (final Exception ex) { @@ -424,8 +522,11 @@ protected Level getDefaultStatus() { } } - protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource, - final byte[] buffer, final String contentType) { + protected void createAdvertiser( + final String advertiserString, + final ConfigurationSource configSource, + final byte[] buffer, + final String contentType) { if (advertiserString != null) { final Node node = new Node(null, advertiserString, null); final Map attributes = node.getAttributes(); @@ -446,12 +547,14 @@ private void setupAdvertisement() { if (type != null) { final Class clazz = type.getPluginClass().asSubclass(Advertiser.class); try { - advertiser = clazz.newInstance(); + advertiser = LoaderUtil.newInstanceOf(clazz); 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); + } catch (final ReflectiveOperationException e) { + LOGGER.error( + "{} attempting to instantiate advertiser: {}", + e.getClass().getSimpleName(), + nodeName, + e); } } } @@ -486,49 +589,157 @@ protected void preConfigure(final Node node) { } } + /** + * 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.getElementName())) { + 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 = (Arbiter) createPluginObject(type, child, null); + 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 (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 = (SelectArbiter) createPluginObject(type, selectNode, null); + final List conditions = new ArrayList<>(); + for (Node child : selectNode.getChildren()) { + final PluginType nodeType = child.getType(); + if (nodeType != null) { + if (Arbiter.class.isAssignableFrom(nodeType.getPluginClass())) { + final Arbiter condition = (Arbiter) createPluginObject(nodeType, child, null); + conditions.add(condition); + child.setObject(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 (Node child : selectNode.getChildren()) { + if (condition == child.getObject()) { + addList.addAll(child.getChildren()); + processConditionals(child); + } + } + } + return addList; + } + protected void doConfigure() { + 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()); + // Find the "Properties" node first + final List children = rootNode.getChildren(); + boolean hasProperties = false; + for (final Node child : children) { + if ("Properties".equalsIgnoreCase(child.getName())) { + hasProperties = true; + createConfiguration(child, null); + if (child.getObject() != null) { + final StrLookup lookup = child.getObject(); + runtimeStrSubstitutor.setVariableResolver(lookup); + configurationStrSubstitutor.setVariableResolver(lookup); + } + break; } - } else { + } + if (!hasProperties) { 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); + final Interpolator interpolator = new Interpolator(lookup, pluginPackages); + interpolator.setConfiguration(this); + interpolator.setLoggerContext(loggerContext.get()); + runtimeStrSubstitutor.setVariableResolver(interpolator); + configurationStrSubstitutor.setVariableResolver(interpolator); + } + + for (final Node child : children) { + if ("Scripts".equalsIgnoreCase(child.getName())) { + createConfiguration(child, null); + if (child.getObject() != null) { + 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); + } + } + } + break; + } } boolean setLoggers = false; boolean setRoot = false; - for (final Node child : rootNode.getChildren()) { - if (child.getName().equalsIgnoreCase("Properties")) { - if (tempLookup == subst.getVariableResolver()) { - LOGGER.error("Properties declaration must be the first element in the configuration"); - } + for (final Node child : children) { + if ("Properties".equalsIgnoreCase(child.getName()) || "Scripts".equalsIgnoreCase(child.getName())) { + // We already used this node continue; } createConfiguration(child, null); if (child.getObject() == null) { 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); - }} - } - } else if (child.getName().equalsIgnoreCase("Appenders")) { + if ("Appenders".equalsIgnoreCase(child.getName())) { appenders = child.getObject(); } else if (child.isInstanceOf(Filter.class)) { addFilter(child.getObject(Filter.class)); - } else if (child.getName().equalsIgnoreCase("Loggers")) { + } else if (child.isInstanceOf(Loggers.class)) { final Loggers l = child.getObject(); loggerConfigs = l.getMap(); setLoggers = true; @@ -536,17 +747,30 @@ protected void doConfigure() { root = l.getRoot(); setRoot = true; } - } else if (child.getName().equalsIgnoreCase("CustomLevels")) { + } else if (child.isInstanceOf(CustomLevels.class)) { customLevels = child.getObject(CustomLevels.class).getCustomLevels(); } else if (child.isInstanceOf(CustomLevelConfig.class)) { final List copy = new ArrayList<>(customLevels); copy.add(child.getObject(CustomLevelConfig.class)); customLevels = copy; + } else if (child.isInstanceOf(AsyncWaitStrategyFactoryConfig.class)) { + final AsyncWaitStrategyFactoryConfig awsfc = child.getObject(AsyncWaitStrategyFactoryConfig.class); + asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory(); + } else if (child.isInstanceOf(MonitorResources.class)) { + monitorResources = child.getObject(MonitorResources.class).getResources(); } else { - final List expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"", - "\"Scripts\"", "\"CustomLevels\""); - LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.", - child.getName(), child.getObject().getClass().getName(), expected); + final List expected = Arrays.asList( + "\"Appenders\"", + "\"Loggers\"", + "\"Properties\"", + "\"Scripts\"", + "\"CustomLevels\"", + "\"MonitorResources\""); + LOGGER.error( + "Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.", + child.getName(), + child.getObject().getClass().getName(), + expected); } } @@ -555,7 +779,8 @@ protected void doConfigure() { setToDefault(); return; } else if (!setRoot) { - LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender"); + LOGGER.warn( + "No Root logger was configured, creating default ERROR-level Root logger with Console appender"); setToDefault(); // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers } @@ -567,34 +792,34 @@ protected void doConfigure() { if (app != null) { loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter()); } else { - LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(), - loggerConfig); + LOGGER.error( + "Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(), loggerConfig); } } - } setParents(); } + public static Level getDefaultLevel() { + final String levelName = PropertiesUtil.getProperties() + .getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, Level.ERROR.name()); + return Level.valueOf(levelName); + } + 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) - .build(); - final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout); + // LOG4J2-3431 don't set a default name if one has already been set + if (Strings.isBlank(getName())) { + // LOG4J2-1176 facilitate memory leak investigation + setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode())); + } + final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(DefaultLayout.INSTANCE); appender.start(); addAppender(appender); final LoggerConfig rootLoggerConfig = getRootLogger(); rootLoggerConfig.addAppender(appender, null, null); - final Level defaultLevel = Level.ERROR; - final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, - defaultLevel.name()); - final Level level = Level.valueOf(levelName); - rootLoggerConfig.setLevel(level != null ? level : defaultLevel); + rootLoggerConfig.setLevel(getDefaultLevel()); } /** @@ -645,7 +870,7 @@ public void removeListener(final ConfigurationListener listener) { @Override @SuppressWarnings("unchecked") public T getAppender(final String appenderName) { - return (T) appenders.get(appenderName); + return appenderName != null ? (T) appenders.get(appenderName) : null; } /** @@ -665,12 +890,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 @@ -697,15 +929,18 @@ public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfi /** * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is * being updated at the same time. - * + *

* Note: This method is not used when configuring via configuration. It is primarily used by unit tests. - * + *

* @param logger The Logger the Appender will be associated with. * @param appender The Appender. */ @Override - public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger, - final Appender appender) { + 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); @@ -724,9 +959,9 @@ public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.L /** * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being * updated at the same time. - * + *

* Note: This method is not used when configuring via configuration. It is primarily used by unit tests. - * + *

* @param logger The Logger the Footer will be associated with. * @param filter The Filter. */ @@ -749,14 +984,15 @@ public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Log /** * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being * updated at the same time. - * + *

* Note: This method is not used when configuring via configuration. It is primarily used by unit tests. - * + *

* @param logger The Logger the Appender will be associated with. * @param additive True if the LoggerConfig should be additive, false otherwise. */ @Override - public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) { + public synchronized void setLoggerAdditive( + final org.apache.logging.log4j.core.Logger logger, final boolean additive) { final String loggerName = logger.getName(); final LoggerConfig lc = getLoggerConfig(loggerName); if (lc.getName().equals(loggerName)) { @@ -781,7 +1017,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(); @@ -900,6 +1136,21 @@ public void createConfiguration(final Node node, final LogEvent event) { } } + /** + * This method is used by Arbiters to create specific children. + * @param type The PluginType. + * @param node The Node. + * @return The created object or null; + */ + public Object createPluginObject(final PluginType type, final Node node) { + if (getState() == State.INITIALIZING) { + return createPluginObject(type, node, null); + } else { + LOGGER.warn("Plugin Object creation is not allowed after initialization"); + return null; + } + } + /** * 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 @@ -910,7 +1161,7 @@ public void createConfiguration(final Node node, final LogEvent event) { * . 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 @@ -918,16 +1169,18 @@ public void createConfiguration(final Node node, final LogEvent event) { * 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 @@ -955,7 +1208,11 @@ private Object createPluginObject(final PluginType type, final Node node, fin } } - return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build(); + return new PluginBuilder(type) + .withConfiguration(this) + .withConfigurationNode(node) + .forLogEvent(event) + .build(); } private static Map createPluginMap(final Node node) { 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..2dfd13b2a8c 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 @@ -1,23 +1,22 @@ /* * 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.logging.log4j.core.config; import java.util.Objects; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; @@ -32,6 +31,11 @@ */ public class AppenderControl extends AbstractFilterable { + /** + * The empty array. + */ + static final AppenderControl[] EMPTY_ARRAY = {}; + private final ThreadLocal recursive = new ThreadLocal<>(); private final Appender appender; private final Level level; @@ -40,14 +44,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; + this.appender = Objects.requireNonNull(appender, "appender"); this.appenderName = appender.getName(); this.level = level; this.intLevel = level == null ? Level.ALL.intLevel() : level.intLevel(); @@ -56,7 +60,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 +69,7 @@ public String getAppenderName() { /** * Returns the Appender. - * + * * @return the Appender. */ public Appender getAppender() { @@ -74,7 +78,7 @@ public Appender getAppender() { /** * Call the appender. - * + * * @param event The event to process. */ public void callAppender(final LogEvent event) { @@ -154,15 +158,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..4c76a868319 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -20,8 +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 +31,10 @@ */ @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 = AppenderControl.EMPTY_ARRAY; /** * 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, AppenderControl.EMPTY_ARRAY); } 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..ebbe1c07e6e 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..0afbf26a7bd 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 @@ -1,24 +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. */ package org.apache.logging.log4j.core.config; 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; @@ -28,11 +27,10 @@ /** * An Appender container. */ -@Plugin(name = "appenders", category = Core.CATEGORY_NAME) +@Plugin(name = "Appenders", category = Core.CATEGORY_NAME) public final class AppendersPlugin { - private AppendersPlugin() { - } + private AppendersPlugin() {} /** * Creates a Map of the Appenders. @@ -41,9 +39,9 @@ private AppendersPlugin() { */ @PluginFactory public static ConcurrentMap createAppenders( - @PluginElement("Appenders") final Appender[] appenders) { + @PluginElement("Appenders") final Appender[] appenders) { - final ConcurrentMap map = new ConcurrentHashMap<>(appenders.length); + final ConcurrentMap map = new ConcurrentHashMap<>(appenders.length); for (final Appender appender : appenders) { map.put(appender.getName(), appender); 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..fc6699f9de5 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config; import java.util.Objects; @@ -24,7 +23,6 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; @@ -35,10 +33,10 @@ * ReliabilityStrategy that counts the number of threads that have started to log an event but have not completed yet, * and waits for these threads to finish before allowing the appenders to be stopped. */ -public class AwaitCompletionReliabilityStrategy implements ReliabilityStrategy { +public class AwaitCompletionReliabilityStrategy implements ReliabilityStrategy, LocationAwareReliabilityStrategy { 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,14 +47,20 @@ 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) */ @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) { + public void log( + final Supplier reconfigured, + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t) { final LoggerConfig config = getActiveLoggerConfig(reconfigured); try { @@ -68,7 +72,32 @@ 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 +113,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 +123,9 @@ 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 +153,7 @@ private void signalCompletionIfShutdown() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -162,7 +193,7 @@ private void waitForCompletion() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) @@ -171,5 +202,4 @@ private void waitForCompletion() { public void beforeStopConfiguration(final Configuration configuration) { // no action } - } 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..bb202789df4 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 @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.config; import java.util.Objects; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; @@ -30,7 +28,7 @@ /** * Reliability strategy that sleeps unconditionally for some time before allowing a Configuration to be stopped. */ -public class AwaitUnconditionallyReliabilityStrategy implements ReliabilityStrategy { +public class AwaitUnconditionallyReliabilityStrategy implements ReliabilityStrategy, LocationAwareReliabilityStrategy { private static final long DEFAULT_SLEEP_MILLIS = 5000; // 5 seconds private static final long SLEEP_MILLIS = sleepMillis(); @@ -41,26 +39,52 @@ public AwaitUnconditionallyReliabilityStrategy(final LoggerConfig loggerConfig) } private static long sleepMillis() { - return PropertiesUtil.getProperties().getLongProperty("log4j.waitMillisBeforeStopOldConfig", - DEFAULT_SLEEP_MILLIS); + return PropertiesUtil.getProperties() + .getLongProperty("log4j.waitMillisBeforeStopOldConfig", 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) */ @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) { + public void log( + final Supplier reconfigured, + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t) { loggerConfig.log(loggerName, fqcn, marker, level, data, t); } /* * (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 +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) @@ -83,7 +107,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#afterLogEvent() */ @Override @@ -93,7 +117,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -103,7 +127,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) @@ -119,5 +143,4 @@ public void beforeStopConfiguration(final Configuration 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..744bc61eee8 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 @@ -1,24 +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. */ 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,7 +25,9 @@ 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; @@ -116,6 +117,14 @@ public interface Configuration extends Filterable { StrSubstitutor getStrSubstitutor(); + default StrSubstitutor getConfigurationStrSubstitutor() { + final StrSubstitutor defaultSubstitutor = getStrSubstitutor(); + if (defaultSubstitutor == null) { + return new ConfigurationStrSubstitutor(); + } + return new ConfigurationStrSubstitutor(defaultSubstitutor); + } + void createConfiguration(Node node, LogEvent event); T getComponent(String name); @@ -135,7 +144,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 +179,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/ConfigurationAware.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationAware.java index fa43f6fd75c..a9c2902ec17 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationAware.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationAware.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config; /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationException.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationException.java index 830cdcf58cd..6a20c31348d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationException.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationException.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -51,5 +51,4 @@ public ConfigurationException(final String message, final Throwable cause) { public ConfigurationException(final Throwable cause) { super(cause); } - } 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..505ca9c9cd9 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 @@ -1,26 +1,25 @@ /* * 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.logging.log4j.core.config; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URL; +import java.net.URISyntaxException; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -28,7 +27,6 @@ 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; @@ -36,9 +34,12 @@ 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.ConfigurationStrSubstitutor; import org.apache.logging.log4j.core.lookup.Interpolator; import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.core.util.BasicAuthorizationProvider; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.core.util.ReflectionUtil; import org.apache.logging.log4j.status.StatusLogger; @@ -72,7 +73,6 @@ public abstract class ConfigurationFactory extends ConfigurationBuilderFactory { public ConfigurationFactory() { - super(); // TEMP For breakpoints } @@ -86,6 +86,12 @@ public ConfigurationFactory() { */ public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile"; + public static final String LOG4J1_CONFIGURATION_FILE_PROPERTY = "log4j.configuration"; + + public static final String LOG4J1_EXPERIMENTAL = "log4j1.compatibility"; + + public static final String AUTHORIZATION_PROVIDER = "authorizationProvider"; + /** * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin} * class. @@ -109,6 +115,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,14 +128,23 @@ public ConfigurationFactory() { */ private static final String CLASS_PATH_SCHEME = "classpath"; - private static volatile List factories = null; + private static final String OVERRIDE_PARAM = "override"; + + private static volatile List factories; private static ConfigurationFactory configFactory = new Factory(); - protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator()); + protected final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator()); private static final Lock LOCK = new ReentrantLock(); + private static final String HTTPS = "https"; + private static final String HTTP = "http"; + + private static final String[] PREFIXES = {"log4j2.", "log4j2.Configuration."}; + + private static volatile AuthorizationProvider authorizationProvider; + /** * Returns the ConfigurationFactory. * @return the ConfigurationFactory. @@ -139,7 +157,8 @@ public static ConfigurationFactory getInstance() { try { if (factories == null) { final List list = new ArrayList<>(); - final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); + final PropertiesUtil props = PropertiesUtil.getProperties(); + final String factoryClass = props.getStringProperty(CONFIGURATION_FACTORY_PROPERTY); if (factoryClass != null) { addFactory(list, factoryClass); } @@ -161,6 +180,7 @@ public static ConfigurationFactory getInstance() { // see above comments about double-checked locking //noinspection NonThreadSafeLazyInitialization factories = Collections.unmodifiableList(list); + authorizationProvider = authorizationProvider(props); } } finally { LOCK.unlock(); @@ -171,16 +191,43 @@ public static ConfigurationFactory getInstance() { return configFactory; } + public static AuthorizationProvider authorizationProvider(final PropertiesUtil props) { + final String authClass = props.getStringProperty(PREFIXES, AUTHORIZATION_PROVIDER, null); + AuthorizationProvider provider = null; + if (authClass != null) { + try { + 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()); + } + } catch (Exception ex) { + LOGGER.warn("Unable to create {}, using default: {}", authClass, ex.getMessage()); + } + } + if (provider == null) { + provider = new BasicAuthorizationProvider(props); + } + return provider; + } + + public static AuthorizationProvider getAuthorizationProvider() { + return authorizationProvider; + } + private static void addFactory(final Collection list, final String factoryClass) { try { - addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); + addFactory(list, Loader.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); } catch (final Exception ex) { LOGGER.error("Unable to load class {}", factoryClass, ex); } } - private static void addFactory(final Collection list, - final Class factoryClass) { + private static void addFactory( + final Collection list, final Class factoryClass) { try { list.add(ReflectionUtil.instantiate(factoryClass)); } catch (final Exception ex) { @@ -216,6 +263,18 @@ public static void removeConfigurationFactory(final ConfigurationFactory factory protected abstract String[] getSupportedTypes(); + protected String getTestPrefix() { + return TEST_PREFIX; + } + + protected String getDefaultPrefix() { + return DEFAULT_PREFIX; + } + + protected String getVersion() { + return LOG4J2_VERSION; + } + protected boolean isActive() { return true; } @@ -229,7 +288,8 @@ protected boolean isActive() { * @param configLocation The configuration location. * @return The Configuration. */ - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + public Configuration getConfiguration( + final LoggerContext loggerContext, final String name, final URI configLocation) { if (!isActive()) { return null; } @@ -252,7 +312,8 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final S * * @return The Configuration. */ - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) { + public Configuration getConfiguration( + final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) { if (!isActive()) { return null; } @@ -290,23 +351,9 @@ static String extractClassLoaderUriPath(final URI uri) { * @param loader The default ClassLoader to use. * @return The InputSource to use to read the configuration. */ + @Deprecated 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; - } + return ConfigurationSource.fromUri(NetUtils.toURI(config)); } /** @@ -323,34 +370,50 @@ private static class Factory extends ConfigurationFactory { * @return The Configuration. */ @Override - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + 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)); + final String configLocationStr = this.substitutor.replace( + PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY)); if (configLocationStr != null) { - final String[] sources = configLocationStr.split(","); + 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 && config instanceof AbstractConfiguration) { - configs.add((AbstractConfiguration) config); + if (config != null) { + if (config instanceof AbstractConfiguration) { + configs.add((AbstractConfiguration) config); + } else { + LOGGER.error("Failed to created configuration at {}", sourceLocation); + return null; + } } else { - LOGGER.error("Failed to created configuration at {}", sourceLocation); - return null; + LOGGER.warn("Unable to create configuration for {}, ignoring", sourceLocation); } } - return new CompositeConfiguration(configs); + if (configs.size() > 1) { + return new CompositeConfiguration(configs); + } else if (configs.size() == 1) { + return configs.get(0); + } } return getConfiguration(loggerContext, configLocationStr); } + final String log4j1ConfigStr = this.substitutor.replace( + PropertiesUtil.getProperties().getStringProperty(LOG4J1_CONFIGURATION_FILE_PROPERTY)); + if (log4j1ConfigStr != null) { + System.setProperty(LOG4J1_EXPERIMENTAL, "true"); + return getConfiguration(LOG4J1_VERSION, loggerContext, log4j1ConfigStr); + } 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); + final Configuration config = + factory.getConfiguration(loggerContext, name, configLocation); if (config != null) { return config; } @@ -359,6 +422,20 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final S } } } else { + 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); + } // configLocation != null final String configLocationStr = configLocation.toString(); for (final ConfigurationFactory factory : getFactories()) { @@ -366,7 +443,8 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final S if (types != null) { for (final String type : types) { if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { - final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); + final Configuration config = + factory.getConfiguration(loggerContext, name, configLocation); if (config != null) { return config; } @@ -389,16 +467,22 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final S 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"); + 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.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) { + 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)); @@ -412,6 +496,9 @@ private Configuration getConfiguration(final LoggerContext loggerContext, final } if (source != null) { for (final ConfigurationFactory factory : getFactories()) { + if (requiredVersion != null && !factory.getVersion().equals(requiredVersion)) { + continue; + } final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { @@ -428,13 +515,14 @@ private Configuration getConfiguration(final LoggerContext loggerContext, final return null; } - private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) { + 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(); + final String prefix = isTest ? factory.getTestPrefix() : factory.getDefaultPrefix(); + final String[] types = factory.getSupportedTypes(); if (types == null) { continue; } @@ -448,7 +536,10 @@ private Configuration getConfiguration(final LoggerContext loggerContext, final 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()); + LOGGER.error( + "Found configuration file `{}` for the inactive `{}`. This `ConfigurationFactory` implementation might be inactive due to a missing dependency.", + configName, + factory.getClass().getName()); } return factory.getConfiguration(loggerContext, source); } @@ -486,6 +577,41 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final C 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 (String pair : pairs) { + final int idx = pair.indexOf("="); + try { + final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; + if (key.equalsIgnoreCase(OVERRIDE_PARAM)) { + locations.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + } + } catch (UnsupportedEncodingException ex) { + LOGGER.warn("Invalid query parameter in {}", configLocations); + } + } + return locations.toArray(Strings.EMPTY_ARRAY); + } + 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 (URISyntaxException ex) { + LOGGER.warn("Error parsing URI {}", configLocations); + } + return new String[] {configLocations}; + } } static List getFactories() { 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..9b920a45241 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.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; + +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, + long lastModifiedMillis) { + super(configuration, reconfigurable, configurationListeners); + this.lastModifiedMillis = lastModifiedMillis; + } + + @Override + 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, 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/ConfigurationListener.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationListener.java index 3650b75ebc1..e02be0ad9d7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationListener.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationListener.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..c71911990a9 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -23,7 +23,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.util.CronExpression; @@ -38,8 +37,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; @@ -48,7 +47,6 @@ public ConfigurationScheduler() { } public ConfigurationScheduler(final String name) { - super(); this.name = name; } @@ -129,7 +127,6 @@ public ScheduledFuture schedule(final Runnable command, final long delay, fin return getExecutorService().schedule(command, delay, unit); } - /** * Creates and executes an action that first based on a cron expression. * @param cronExpression the cron expression describing the schedule. @@ -147,17 +144,18 @@ public CronScheduledFuture scheduleWithCron(final CronExpression cronExpressi * @param command The Runnable to run, * @return a ScheduledFuture representing the next time the command will run. */ - public CronScheduledFuture scheduleWithCron(final CronExpression cronExpression, final Date startDate, final Runnable command) { + public CronScheduledFuture scheduleWithCron( + final CronExpression cronExpression, final Date startDate, final Runnable command) { final Date fireDate = cronExpression.getNextValidTimeAfter(startDate == null ? new Date() : startDate); final CronRunnable runnable = new CronRunnable(command, cronExpression); final ScheduledFuture future = schedule(runnable, nextFireInterval(fireDate), TimeUnit.MILLISECONDS); final CronScheduledFuture cronScheduledFuture = new CronScheduledFuture<>(future, fireDate); runnable.setScheduledFuture(cronScheduledFuture); - LOGGER.debug("{} scheduled cron expression {} to fire at {}", name, cronExpression.getCronExpression(), fireDate); + LOGGER.debug( + "{} scheduled cron expression {} to fire at {}", name, cronExpression.getCronExpression(), fireDate); return cronScheduledFuture; } - /** * Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently * with the given period; that is executions will commence after initialDelay then initialDelay+period, @@ -169,7 +167,8 @@ public CronScheduledFuture scheduleWithCron(final CronExpression cronExpressi * @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an * exception upon cancellation */ - public ScheduledFuture scheduleAtFixedRate(final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { + public ScheduledFuture scheduleAtFixedRate( + final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { return getExecutorService().scheduleAtFixedRate(command, initialDelay, period, unit); } @@ -183,7 +182,8 @@ public ScheduledFuture scheduleAtFixedRate(final Runnable command, final long * @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an * exception upon cancellation */ - public ScheduledFuture scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) { + public ScheduledFuture scheduleWithFixedDelay( + final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) { return getExecutorService().scheduleWithFixedDelay(command, initialDelay, delay, unit); } @@ -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; @@ -237,12 +241,15 @@ public void run() { } } runnable.run(); - } catch(final Throwable ex) { + } catch (final Throwable ex) { LOGGER.error("{} caught error running command", name, ex); } finally { final Date fireDate = cronExpression.getNextValidTimeAfter(new Date()); final ScheduledFuture future = schedule(this, nextFireInterval(fireDate), TimeUnit.MILLISECONDS); - LOGGER.debug("{} Cron expression {} scheduled to fire again at {}", name, cronExpression.getCronExpression(), + LOGGER.debug( + "{} Cron expression {} scheduled to fire again at {}", + name, + cronExpression.getCronExpression(), fireDate); scheduledFuture.reset(future, fireDate); } @@ -273,5 +280,4 @@ public String toString() { sb.append("]"); return sb.toString(); } - } 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..d6143cdc158 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 @@ -1,22 +1,22 @@ /* * 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.logging.log4j.core.config; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -24,79 +24,148 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.JarURLConnection; import java.net.MalformedURLException; 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 org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; 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.status.StatusLogger; +import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.LoaderUtil; /** * Represents the source for the logging configuration. */ +/*@NullMarked*/ public class ConfigurationSource { + + private static final Logger LOGGER = StatusLogger.getLogger(); + /** * 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(Constants.EMPTY_BYTE_ARRAY, null, 0); + + /** + * ConfigurationSource to use with {@link org.apache.logging.log4j.core.config.composite.CompositeConfiguration}. + */ + public static final ConfigurationSource COMPOSITE_SOURCE = + new ConfigurationSource(Constants.EMPTY_BYTE_ARRAY, null, 0); - private final File file; - private final URL url; - private final String location; private final InputStream stream; - private final byte[] data; + private volatile byte /*@Nullable*/[] data; + private final /*@Nullable*/ Source source; + // The initial modification time when the `ConfigurationSource` is created + private final long initialLastModified; + // Set when the configuration has been updated so reset can use it for the next lastModified timestamp. + private volatile long currentLastModified; /** * 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 (Exception ex) { + // There is a problem with the file. It will be handled somewhere else. + } + this.currentLastModified = this.initialLastModified = 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.currentLastModified = this.initialLastModified = modified; } /** * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified - * url. + * URL. * - * @param stream the input stream + * @param stream the input stream, the caller is responsible for closing this resource. * @param url the URL where the input stream originated */ public ConfigurationSource(final InputStream stream, final URL url) { + this(stream, url, 0); + } + + /** + * 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, final long lastModified) { 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.currentLastModified = this.initialLastModified = 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)); + public ConfigurationSource(InputStream stream) throws IOException { + this(toByteArray(stream), null, 0); + } + + /** + * Constructs a new {@code ConfigurationSource} with the specified source. + * + * @param source a Source. + * @param data data from the source + * @param lastModified when the source was last modified. + */ + public ConfigurationSource(final Source source, final byte[] data, final long lastModified) { + Objects.requireNonNull(source, "source is null"); + this.data = Objects.requireNonNull(data, "data is null"); + this.stream = new ByteArrayInputStream(data); + this.currentLastModified = this.initialLastModified = lastModified; + this.source = source; } - private ConfigurationSource(final byte[] data) { + 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.file = null; - this.url = null; - this.location = null; + this.currentLastModified = this.initialLastModified = lastModified; + this.source = url == null ? null : new Source(url); } /** @@ -125,8 +194,12 @@ private static byte[] toByteArray(final InputStream inputStream) throws IOExcept * * @return the configuration source file, or {@code null} */ - public File getFile() { - return file; + public /*@Nullable*/ File getFile() { + return source == null ? null : source.getFile(); + } + + private boolean isLocation() { + return source != null && source.getLocation() != null; } /** @@ -135,39 +208,46 @@ public File getFile() { * * @return the configuration source URL, or {@code null} */ - public URL getURL() { - return url; + public /*@Nullable*/ URL getURL() { + return source == null ? null : source.getURL(); + } + + /** + * @deprecated Not used internally, no replacement. + */ + @Deprecated + public void setSource(final Source ignored) { + LOGGER.warn("Ignoring call of deprecated method `ConfigurationSource#setSource()`."); + } + + public void setData(final byte[] data) { + this.data = data; + } + + /** + * Updates the last known modification time of the resource. + * + * @param currentLastModified The modification time of the resource in millis. + */ + public void setModifiedMillis(final long currentLastModified) { + this.currentLastModified = currentLastModified; } /** * Returns a URI representing the configuration resource or null if it cannot be determined. * @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; + public /*@Nullable*/ URI getURI() { + return source == null ? null : source.getURI(); + } + + /** + * Returns the last modification time known when the {@code ConfigurationSource} was created. + * + * @return the last modified time of the resource. + */ + public long getLastModified() { + return initialLastModified; } /** @@ -176,8 +256,8 @@ public URI getURI() { * * @return a string describing the configuration source file or URL, or {@code null} */ - public String getLocation() { - return location; + public /*@Nullable*/ String getLocation() { + return source == null ? null : source.getLocation(); } /** @@ -195,24 +275,36 @@ public InputStream getInputStream() { * @return a new {@code ConfigurationSource} * @throws IOException if a problem occurred while opening the new input stream */ - public ConfigurationSource resetInputStream() throws IOException { + public /*@Nullable*/ ConfigurationSource resetInputStream() throws IOException { + byte[] data = this.data; + if (source != null && data != null) { + return new ConfigurationSource(source, data, currentLastModified); + } + File file = getFile(); 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); + return new ConfigurationSource(Files.newInputStream(file.toPath()), getFile()); + } + URL url = getURL(); + if (url != null && data != null) { + // Creates a ConfigurationSource without accessing the URL since the data was provided. + return new ConfigurationSource(data, url, currentLastModified); } + URI uri = getURI(); + if (uri != null) { + return fromUri(uri); + } + return data != null ? new ConfigurationSource(data, null, currentLastModified) : null; } @Override public String toString() { - if (location != null) { - return location; + if (source != null) { + return source.getLocation(); } if (this == NULL_SOURCE) { return "NULL_SOURCE"; } + byte[] data = this.data; final int length = data == null ? -1 : data.length; return "stream (" + length + " bytes, unknown location)"; } @@ -220,9 +312,9 @@ public String toString() { /** * Loads the configuration from a URI. * @param configLocation A URI representing the location of the configuration. - * @return The ConfigurationSource for the configuration. + * @return The ConfigurationSource for the configuration or {@code null}. */ - public static ConfigurationSource fromUri(final URI configLocation) { + public static /*@Nullable*/ ConfigurationSource fromUri(final URI configLocation) { final File configFile = FileUtils.fileFromUri(configLocation); if (configFile != null && configFile.exists() && configFile.canRead()) { try { @@ -234,21 +326,17 @@ 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()); + 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; } @@ -259,30 +347,39 @@ public static ConfigurationSource fromUri(final URI configLocation) { * @param loader The default ClassLoader to use. * @return The ConfigurationSource for the configuration. */ - public static ConfigurationSource fromResource(final String resource, final ClassLoader loader) { + public static /*@Nullable*/ ConfigurationSource fromResource(String resource, /*@Nullable*/ ClassLoader loader) { final URL url = Loader.getResource(resource, loader); - if (url == null) { - return null; - } - InputStream is = null; + return url == null ? null : getConfigurationSource(url); + } + + @SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "The name of the accessed files is based on a configuration value.") + private static /*@Nullable*/ 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 File file = FileUtils.fileFromUri(url.toURI()); + final URLConnection urlConnection = UrlConnectionFactory.createConnection(url); 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 if (urlConnection instanceof JarURLConnection) { + // Work around https://bugs.openjdk.java.net/browse/JDK-6956385. + URL jarFileUrl = ((JarURLConnection) urlConnection).getJarFileURL(); + File jarFile = new File(jarFileUrl.getFile()); + long lastModified = jarFile.lastModified(); + return new ConfigurationSource(urlConnection.getInputStream(), url, lastModified); + } else { + return new ConfigurationSource( + urlConnection.getInputStream(), url, urlConnection.getLastModified()); + } + } catch (FileNotFoundException ex) { + ConfigurationFactory.LOGGER.info("Unable to locate file {}, ignoring.", url.toString()); + return null; } + } catch (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..58360d6e93d 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 @@ -1,27 +1,25 @@ /* * 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.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; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,6 +28,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 +45,17 @@ private static Log4jContextFactory getFactory() { final LoggerContextFactory factory = LogManager.getFactory(); if (factory instanceof Log4jContextFactory) { return (Log4jContextFactory) factory; - } else 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; + } + if (factory != null) { + LOGGER.error( + "LogManager returned an instance of {} which does not implement {}. Unable to initialize Log4j.", + factory.getClass().getName(), + Log4jContextFactory.class.getName()); } else { - LOGGER.fatal("LogManager did not return a LoggerContextFactory. This indicates something has gone terribly wrong!"); - return null; + LOGGER.fatal( + "LogManager did not return a LoggerContextFactory. This indicates something has gone terribly wrong!"); } + return null; } /** @@ -62,8 +64,7 @@ private static Log4jContextFactory getFactory() { * @param source The InputSource for the configuration. * @return The LoggerContext. */ - public static LoggerContext initialize(final ClassLoader loader, - final ConfigurationSource source) { + public static LoggerContext initialize(final ClassLoader loader, final ConfigurationSource source) { return initialize(loader, source, null); } @@ -74,18 +75,15 @@ public static LoggerContext initialize(final ClassLoader loader, * @param externalContext The external context to be attached to the LoggerContext. * @return The LoggerContext. */ - - public static LoggerContext initialize(final ClassLoader loader, - final ConfigurationSource source, - final Object externalContext) - { + public static LoggerContext initialize( + final ClassLoader loader, final ConfigurationSource source, final Object externalContext) { try { final Log4jContextFactory factory = getFactory(); - return factory == null ? null : - factory.getContext(FQCN, loader, externalContext, false, source); + return factory == null ? null : factory.getContext(FQCN, loader, externalContext, false, source); } catch (final Exception ex) { - LOGGER.error("There was a problem obtaining a LoggerContext using the configuration source [{}]", source, ex); + LOGGER.error( + "There was a problem obtaining a LoggerContext using the configuration source [{}]", source, ex); } return null; } @@ -99,7 +97,6 @@ public static LoggerContext initialize(final ClassLoader loader, */ public static LoggerContext initialize(final String name, final ClassLoader loader, final String configLocation) { return initialize(name, loader, configLocation, null); - } /** @@ -110,25 +107,14 @@ public static LoggerContext initialize(final String name, final ClassLoader load * @param externalContext The external context to be attached to the LoggerContext * @return The LoggerContext or null if an error occurred (check the status logger). */ - public static LoggerContext initialize(final String name, final ClassLoader loader, final String configLocation, - final Object externalContext) { + public static LoggerContext initialize( + final String name, final ClassLoader loader, final String configLocation, final Object externalContext) { 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); } /** @@ -150,30 +136,67 @@ public static LoggerContext initialize(final String name, final ClassLoader load * @param externalContext The external context to be attached to the LoggerContext * @return The LoggerContext. */ - public static LoggerContext initialize(final String name, final ClassLoader loader, final URI configLocation, - final Object externalContext) { + public static LoggerContext initialize( + final String name, final ClassLoader loader, final URI configLocation, final Object externalContext) { + + try { + final Log4jContextFactory factory = getFactory(); + return factory == null + ? null + : factory.getContext(FQCN, loader, externalContext, false, configLocation, name); + } catch (final Exception ex) { + LOGGER.error( + "There was a problem initializing the LoggerContext [{}] using configuration at [{}].", + name, + configLocation, + ex); + } + 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, externalContext, false, configLocation, name); + 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); + 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, + public static LoggerContext initialize( + final String name, + final ClassLoader loader, + final List configLocations, final Object externalContext) { try { final Log4jContextFactory factory = getFactory(); - return factory == null ? - null : - factory.getContext(FQCN, loader, externalContext, false, configLocations, name); + return factory == null + ? null + : factory.getContext(FQCN, loader, externalContext, false, configLocations, name); } catch (final Exception ex) { - LOGGER.error("There was a problem initializing the LoggerContext [{}] using configurations at [{}].", name, - configLocations, ex); + LOGGER.error( + "There was a problem initializing the LoggerContext [{}] using configurations at [{}].", + name, + configLocations, + ex); } return null; } @@ -214,20 +237,77 @@ public static LoggerContext initialize(final ClassLoader loader, final Configura * @param externalContext - The external context to be attached to the LoggerContext. * @return The LoggerContext. */ - public static LoggerContext initialize(final ClassLoader loader, final Configuration configuration, final Object externalContext) { + public static LoggerContext initialize( + final ClassLoader loader, final Configuration configuration, final Object externalContext) { try { final Log4jContextFactory factory = getFactory(); - return factory == null ? null : - factory.getContext(FQCN, loader, externalContext, false, configuration); + return factory == null ? null : factory.getContext(FQCN, loader, externalContext, false, configuration); } catch (final Exception ex) { - LOGGER.error("There was a problem initializing the LoggerContext using configuration {}", - configuration.getName(), ex); + LOGGER.error( + "There was a problem initializing the LoggerContext using configuration {}", + configuration.getName(), + ex); } 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 +317,8 @@ 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 +331,39 @@ 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 +372,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 +388,8 @@ 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,14 +411,22 @@ 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) { @@ -320,7 +451,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 +469,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/CronScheduledFuture.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CronScheduledFuture.java index 4afb46f9906..63f060be64a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CronScheduledFuture.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CronScheduledFuture.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..3a0ee95bbc7 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 @@ -1,23 +1,22 @@ /* * 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.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; @@ -31,6 +30,11 @@ @Plugin(name = "CustomLevel", category = Core.CATEGORY_NAME, printObject = true) public final class CustomLevelConfig { + /** + * The empty array. + */ + static final CustomLevelConfig[] EMPTY_ARRAY = {}; + private final String levelName; private final int intLevel; @@ -42,15 +46,14 @@ 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. */ @PluginFactory - public static CustomLevelConfig createLevel(// @formatter:off - @PluginAttribute("name") final String levelName, - @PluginAttribute("intLevel") final int intLevel) { + public static CustomLevelConfig createLevel( // @formatter:off + @PluginAttribute("name") final String levelName, @PluginAttribute("intLevel") final int intLevel) { // @formatter:on StatusLogger.getLogger().debug("Creating CustomLevel(name='{}', intValue={})", levelName, intLevel); @@ -60,7 +63,7 @@ public static CustomLevelConfig createLevel(// @formatter:off /** * Returns the custom level name. - * + * * @return the custom level name */ public String getLevelName() { @@ -70,7 +73,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..48dcba6a43d 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 @@ -1,26 +1,24 @@ /* * 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.logging.log4j.core.config; 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; @@ -40,14 +38,14 @@ private CustomLevels(final CustomLevelConfig[] customLevels) { /** * Create a CustomLevels object to contain all the CustomLevelConfigs. - * + * * @param customLevels An array of CustomLevelConfigs. * @return A CustomLevels object. */ @PluginFactory - public static CustomLevels createCustomLevels(// + public static CustomLevels createCustomLevels( // @PluginElement("CustomLevels") final CustomLevelConfig[] customLevels) { - return new CustomLevels(customLevels == null ? new CustomLevelConfig[0] : customLevels); + return new CustomLevels(customLevels == null ? CustomLevelConfig.EMPTY_ARRAY : customLevels); } /** 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..18cb46d6ee4 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 @@ -1,30 +1,29 @@ /* * 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.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; /** * The default advertiser does not do anything. */ -@Plugin(name = "default", category = Node.CATEGORY, elementType = "advertiser", printObject = false) +@Plugin(name = "Default", category = Node.CATEGORY, elementType = "advertiser", printObject = false) public class DefaultAdvertiser implements Advertiser { /** @@ -39,11 +38,10 @@ public Object advertise(final Map properties) { /** * Does nothing. - * @param advertisedObject + * @param advertisedObject the advertised object */ @Override public void unadvertise(final Object advertisedObject) { - //no-op + // no-op } - } 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..03444e8dc66 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -28,12 +28,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"; - + /** * The default Pattern used for the default Layout. */ @@ -48,6 +48,5 @@ public DefaultConfiguration() { } @Override - protected void doConfigure() { - } + protected void doConfigure() {} } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultLayout.java new file mode 100644 index 00000000000..a425d9ac117 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultLayout.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.config; + +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Map; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.status.StatusData; + +/** + * A simple layout used only by {@link DefaultConfiguration} + *

+ * This layout allows to create applications that don't contain {@link org.apache.logging.log4j.core.layout.PatternLayout} + * and all its patterns, e.g. GraalVM applications. + *

+ * + * @since 2.25.0 + */ +final class DefaultLayout implements StringLayout { + + static final StringLayout INSTANCE = new DefaultLayout(); + + private DefaultLayout() {} + + @Override + public String toSerializable(LogEvent event) { + return new StatusData( + event.getSource(), + event.getLevel(), + event.getMessage(), + event.getThrown(), + event.getThreadName()) + .getFormattedStatus() + + System.lineSeparator(); + } + + @Override + public byte[] toByteArray(LogEvent event) { + return toSerializable(event).getBytes(Charset.defaultCharset()); + } + + @Override + public void encode(LogEvent event, ByteBufferDestination destination) { + final byte[] data = toByteArray(event); + destination.writeBytes(data, 0, data.length); + } + + @Override + public String getContentType() { + return "text/plain"; + } + + @Override + public Charset getCharset() { + return Charset.defaultCharset(); + } + + @Override + public byte[] getFooter() { + return null; + } + + @Override + public byte[] getHeader() { + return null; + } + + @Override + public Map getContentFormat() { + return Collections.emptyMap(); + } +} 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..17dbae1b34a 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 @@ -1,24 +1,22 @@ /* * 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.logging.log4j.core.config; import java.util.Objects; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; @@ -28,7 +26,7 @@ /** * Reliability strategy that assumes reconfigurations will never take place. */ -public class DefaultReliabilityStrategy implements ReliabilityStrategy { +public class DefaultReliabilityStrategy implements ReliabilityStrategy, LocationAwareReliabilityStrategy { private final LoggerConfig loggerConfig; @@ -38,20 +36,46 @@ 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) */ @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) { + public void log( + final Supplier reconfigured, + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t) { loggerConfig.log(loggerName, fqcn, marker, level, data, t); } /* * (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 +86,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 +98,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#afterLogEvent() */ @Override @@ -84,7 +108,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -94,7 +118,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) @@ -103,5 +127,4 @@ public void beforeStopAppenders() { public void beforeStopConfiguration(final Configuration configuration) { // no action } - } 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..b6d7f57d663 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.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.logging.log4j.core.config; + +import static java.util.Objects.requireNonNull; +import static org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil.readStream; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.Instant; +import java.util.List; +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.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.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * + */ +@Plugin(name = "http", category = Watcher.CATEGORY, elementType = Watcher.ELEMENT_TYPE, printObject = true) +@PluginAliases("https") +public class HttpWatcher extends AbstractWatcher { + + private final Logger LOGGER = StatusLogger.getLogger(); + + 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); + 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); + // Update lastModifiedMillis + // https://github.com/apache/logging-log4j2/issues/2937 + lastModifiedMillis = source.getLastModified(); + // The result of the HTTP/HTTPS request is already logged at `DEBUG` by `HttpInputStreamUtil` + // We only log the important events at `INFO` or more. + switch (result.getStatus()) { + case NOT_MODIFIED: { + return false; + } + case SUCCESS: { + final ConfigurationSource configSource = getConfiguration().getConfigurationSource(); + try { + // In this case `result.getInputStream()` is not null. + configSource.setData(readStream(requireNonNull(result.getInputStream()))); + configSource.setModifiedMillis(source.getLastModified()); + LOGGER.info( + "{} resource at {} was modified on {}", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm(), + () -> Instant.ofEpochMilli(source.getLastModified())); + return true; + } catch (final IOException e) { + // Dead code since result.getInputStream() is a ByteArrayInputStream + LOGGER.error("Error accessing configuration at {}", url.toExternalForm(), e); + return false; + } + } + case NOT_FOUND: { + LOGGER.warn( + "{} resource at {} was not found", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm()); + return false; + } + default: { + LOGGER.warn( + "Unexpected error retrieving {} resource at {}", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm()); + return false; + } + } + } catch (final URISyntaxException ex) { + LOGGER.error("Bad configuration file URL {}", url.toExternalForm(), ex); + return false; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LocationAwareReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LocationAwareReliabilityStrategy.java new file mode 100644 index 00000000000..b0ff0e0017f --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LocationAwareReliabilityStrategy.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.config; + +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.Supplier; + +/** + * Interface to ensure delivery of log events to the appropriate Appenders while including location information. + */ +public interface LocationAwareReliabilityStrategy { + /** + * 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 + */ + void log( + Supplier reconfigured, + String loggerName, + String fqcn, + StackTraceElement location, + Marker marker, + Level level, + Message data, + Throwable t); +} 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..d8f6b63a678 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 @@ -1,26 +1,24 @@ /* * 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.logging.log4j.core.config; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; @@ -30,10 +28,10 @@ /** * ReliabilityStrategy that uses read/write locks to prevent the LoggerConfig from stopping while it is in use. */ -public class LockingReliabilityStrategy implements ReliabilityStrategy { +public class LockingReliabilityStrategy implements ReliabilityStrategy, LocationAwareReliabilityStrategy { 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,14 +39,20 @@ 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) */ @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) { + public void log( + final Supplier reconfigured, + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t) { final LoggerConfig config = getActiveLoggerConfig(reconfigured); try { @@ -60,7 +64,32 @@ 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 +105,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 +115,9 @@ 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 +138,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -122,7 +153,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) @@ -131,5 +162,4 @@ public void beforeStopAppenders() { public void beforeStopConfiguration(final Configuration configuration) { // no action } - } 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..12b0b2f0abb 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -22,7 +22,6 @@ 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.Marker; @@ -30,16 +29,21 @@ 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.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.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; +import org.apache.logging.log4j.core.impl.LocationAware; import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.impl.ReusableLogEventFactory; import org.apache.logging.log4j.core.lookup.StrSubstitutor; @@ -48,14 +52,14 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.util.LoaderUtil; 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) -public class LoggerConfig extends AbstractFilterable { +@Plugin(name = "Logger", category = Node.CATEGORY, printObject = true) +public class LoggerConfig extends AbstractFilterable implements LocationAware { public static final String ROOT = "root"; private static LogEventFactory LOG_EVENT_FACTORY = null; @@ -75,21 +79,267 @@ public class LoggerConfig extends AbstractFilterable { 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); - } + try { + LOG_EVENT_FACTORY = LoaderUtil.newCheckedInstanceOfProperty( + Constants.LOG4J_LOG_EVENT_FACTORY, + LogEventFactory.class, + LoggerConfig::createDefaultLogEventFactory); + } catch (final Exception ex) { + LOGGER.error("Unable to create LogEventFactory: {}", ex.getMessage(), ex); + LOG_EVENT_FACTORY = createDefaultLogEventFactory(); + } + } + + private static LogEventFactory createDefaultLogEventFactory() { + return Constants.ENABLE_THREADLOCALS ? new ReusableLogEventFactory() : new DefaultLogEventFactory(); + } + + @PluginBuilderFactory + 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; + + @PluginBuilderAttribute + private Level level; + + @PluginBuilderAttribute + private String levelAndRefs; + + @PluginBuilderAttribute("name") + @Required(message = "Loggers cannot be configured without a name") + private String loggerName; + + @PluginBuilderAttribute + private String includeLocation; + + @PluginElement("AppenderRef") + private AppenderRef[] refs; + + @PluginElement("Properties") + private Property[] properties; + + @PluginConfiguration + private Configuration config; + + @PluginElement("Filter") + private Filter filter; + + public boolean isAdditivity() { + return additivity == null || additivity; + } + + /** + * @since 2.26.0 + */ + public B setAdditivity(final boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + public Level getLevel() { + return level; + } + + /** + * @since 2.26.0 + */ + public B setLevel(final Level level) { + this.level = level; + return asBuilder(); } - if (LOG_EVENT_FACTORY == null) { - LOG_EVENT_FACTORY = Constants.ENABLE_THREADLOCALS - ? new ReusableLogEventFactory() - : new DefaultLogEventFactory(); + + public String getLevelAndRefs() { + return levelAndRefs; + } + + /** + * @since 2.26.0 + */ + public B setLevelAndRefs(final String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + public String getLoggerName() { + return loggerName; + } + + /** + * @since 2.26.0 + */ + public B setLoggerName(final String loggerName) { + this.loggerName = loggerName; + return asBuilder(); + } + + public String getIncludeLocation() { + return includeLocation; + } + + /** + * @since 2.26.0 + */ + public B setIncludeLocation(final String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + public AppenderRef[] getRefs() { + return refs; + } + + /** + * @since 2.26.0 + */ + public B setRefs(final AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + public Property[] getProperties() { + return properties; + } + + /** + * @since 2.26.0 + */ + public B setProperties(final Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + public Configuration getConfig() { + return config; + } + + /** + * @since 2.26.0 + */ + public B setConfig(final Configuration config) { + this.config = config; + return asBuilder(); + } + + public Filter getFilter() { + return filter; + } + + /** @since 2.25.0 */ + public B setFilter(final Filter filter) { + this.filter = filter; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setAdditivity(boolean)}. + */ + @Deprecated + public B withAdditivity(final boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setLevel(Level)}. + */ + @Deprecated + public B withLevel(final Level level) { + this.level = level; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setLevelAndRefs(String)}. + */ + @Deprecated + public B withLevelAndRefs(final String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setLoggerName(String)}. + */ + @Deprecated + public B withLoggerName(final String loggerName) { + this.loggerName = loggerName; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setIncludeLocation(String)}. + */ + @Deprecated + public B withIncludeLocation(final String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setRefs(AppenderRef[])}. + */ + @Deprecated + public B withRefs(final AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setProperties(Property[])}. + */ + @Deprecated + public B withProperties(final Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setConfig(Configuration)}. + */ + @Deprecated + public B withConfig(final Configuration config) { + this.config = config; + return asBuilder(); + } + + /** + * @deprecated Use {@link #setFilter(Filter)} instead + */ + @Deprecated + public B withtFilter(final Filter filter) { + return setFilter(filter); + } + + /** @deprecated since 2.25.0. Use {@link #setFilter(Filter)} instead. */ + @Deprecated + public B withFilter(final Filter filter) { + return setFilter(filter); + } + + @Override + public LoggerConfig build() { + final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; + final LevelAndRefs container = LoggerConfig.getLevelAndRefs(level, refs, levelAndRefs, config); + final boolean useLocation = includeLocation(includeLocation, config); + return new LoggerConfig( + name, container.refs, filter, container.level, isAdditivity(), properties, config, useLocation); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; } } @@ -124,8 +374,14 @@ 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, - final Level level, final boolean additive, final Property[] properties, final Configuration config, + 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; @@ -136,8 +392,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 = Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(properties, properties.length))); } else { this.properties = null; } @@ -265,7 +520,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; } /** @@ -373,48 +636,109 @@ public boolean isPropertiesRequireLookup() { * @param t A Throwable or null. */ @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)); - } - } + public void log( + final String loggerName, + final String fqcn, + 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(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(final 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) { + final 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) { + final List results = new ArrayList<>(props.size()); + for (int i = 0; i < props.size(); i++) { + final Property prop = props.get(i); + final String value = prop.evaluate(config.getStrSubstitutor()); // since LOG4J2-1575 + results.add(Property.createProperty(prop.getName(), prop.getRawValue(), 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 + * {@literal 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 +752,50 @@ public ReliabilityStrategy getReliabilityStrategy() { return reliabilityStrategy; } - private void processLogEvent(final LogEvent event) { + /** + * Logs an event, bypassing filters. + * + * @param event The log event. + * @param predicate predicate for which LoggerConfig instances to append to. A + * {@literal null} value is equivalent to a true predicate. + */ + protected void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) { event.setIncludeLocation(isIncludeLocation()); - callAppenders(event); - logParent(event); + if (predicate == null || predicate.allow(this)) { + callAppenders(event); + } + logParent(event, predicate); + } + + @Override + public boolean requiresLocation() { + if (!includeLocation) { + return false; + } + AppenderControl[] controls = appenders.get(); + LoggerConfig loggerConfig = this; + while (loggerConfig != null) { + for (AppenderControl control : controls) { + final Appender appender = control.getAppender(); + if (appender instanceof LocationAware && ((LocationAware) appender).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) { + private void logParent(final LogEvent event, final LoggerConfigPredicate predicate) { if (additive && parent != null) { - parent.log(event); + parent.log(event, predicate); } } @@ -469,7 +828,8 @@ public String toString() { * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} */ @Deprecated - public static LoggerConfig createLogger(final String additivity, + public static LoggerConfig createLogger( + final String additivity, // @formatter:off final Level level, @PluginAttribute("name") final String loggerName, @@ -478,7 +838,7 @@ public static LoggerConfig createLogger(final String additivity, final Property[] properties, @PluginConfiguration final Configuration config, final Filter filter) { - // @formatter:on + // @formatter:on if (loggerName == null) { LOGGER.error("Loggers cannot be configured without a name"); return null; @@ -488,8 +848,15 @@ public static LoggerConfig createLogger(final String additivity, 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)); + return new LoggerConfig( + name, + appenderRefs, + filter, + level, + additive, + properties, + config, + includeLocation(includeLocation, config)); } /** @@ -506,41 +873,290 @@ 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 - ) { + // @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 String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; - return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, - includeLocation(includeLocation)); + return new LoggerConfig( + name, + Arrays.asList(refs), + filter, + level, + additivity, + properties, + config, + includeLocation(includeLocation, config)); + } + + /** + */ + protected static boolean includeLocation(final String includeLocationConfigValue) { + return includeLocation(includeLocationConfigValue, null); } // 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) + @Plugin(name = "Root", category = Core.CATEGORY_NAME, printObject = true) public static class RootLogger extends LoggerConfig { - @PluginFactory + @PluginBuilderFactory + 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 { + + @PluginBuilderAttribute + private boolean additivity; + + @PluginBuilderAttribute + private Level level; + + @PluginBuilderAttribute + private String levelAndRefs; + + @PluginBuilderAttribute + private String includeLocation; + + @PluginElement("AppenderRef") + private AppenderRef[] refs; + + @PluginElement("Properties") + private Property[] properties; + + @PluginConfiguration + private Configuration config; + + @PluginElement("Filter") + private Filter filter; + + public boolean isAdditivity() { + return additivity; + } + + /** + * @since 2.26.0 + */ + public B setAdditivity(final boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + public Level getLevel() { + return level; + } + + /** + * @since 2.26.0 + */ + public B setLevel(final Level level) { + this.level = level; + return asBuilder(); + } + + public String getLevelAndRefs() { + return levelAndRefs; + } + + /** + * @since 2.26.0 + */ + public B setLevelAndRefs(final String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + public String getIncludeLocation() { + return includeLocation; + } + + /** + * @since 2.26.0 + */ + public B setIncludeLocation(final String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + public AppenderRef[] getRefs() { + return refs; + } + + /** + * @since 2.26.0 + */ + public B setRefs(final AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + public Property[] getProperties() { + return properties; + } + + /** + * @since 2.26.0 + */ + public B setProperties(final Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + public Configuration getConfig() { + return config; + } + + /** + * @since 2.26.0 + */ + public B setConfig(final Configuration config) { + this.config = config; + return asBuilder(); + } + + public Filter getFilter() { + return filter; + } + + /** @since 2.25.0 */ + public B setFilter(final Filter filter) { + this.filter = filter; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setAdditivity(boolean)}. + */ + @Deprecated + public B withAdditivity(final boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setLevel(Level)}. + */ + @Deprecated + public B withLevel(final Level level) { + this.level = level; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setLevelAndRefs(String)}. + */ + @Deprecated + public B withLevelAndRefs(final String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setIncludeLocation(String)}. + */ + @Deprecated + public B withIncludeLocation(final String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setRefs(AppenderRef[])}. + */ + @Deprecated + public B withRefs(final AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setProperties(Property[])}. + */ + @Deprecated + public B withProperties(final Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + /** + * @deprecated since 2.26.0 use {@link #setConfig(Configuration)}. + */ + @Deprecated + public B withConfig(final Configuration config) { + this.config = config; + return asBuilder(); + } + + /** + * @deprecated since 2.25.0. Use {@link #setFilter(Filter)} instead. + */ + @Deprecated + public B withtFilter(final Filter filter) { + return setFilter(filter); + } + + @Override + public LoggerConfig build() { + final 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)); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; + } + } + + @Deprecated public static LoggerConfig createLogger( // @formatter:off @PluginAttribute("additivity") final String additivity, @@ -550,14 +1166,79 @@ public static LoggerConfig createLogger( @PluginElement("Properties") final Property[] properties, @PluginConfiguration final Configuration config, @PluginElement("Filter") final Filter filter) { - // @formatter:on + // @formatter:on 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, config)); + } + } - return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, - properties, config, includeLocation(includeLocation)); + protected static LevelAndRefs getLevelAndRefs( + final Level level, final AppenderRef[] refs, final String levelAndRefs, final Configuration config) { + final 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]); + final List refList = new ArrayList<>(); + if (parts.length > 1) { + 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..6953625e4f6 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerContextAware.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.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/Loggers.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Loggers.java index f7b12aeedad..23bfa550f7d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Loggers.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Loggers.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..1a7cdba4887 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 @@ -1,24 +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. */ package org.apache.logging.log4j.core.config; 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; @@ -26,11 +25,10 @@ /** * Container of Logger objects. */ -@Plugin(name = "loggers", category = Node.CATEGORY) +@Plugin(name = "Loggers", category = Node.CATEGORY) public final class LoggersPlugin { - private LoggersPlugin() { - } + private LoggersPlugin() {} /** * Create a Loggers object to contain all the Loggers. @@ -46,7 +44,8 @@ public static Loggers createLoggers(@PluginElement("Loggers") final LoggerConfig if (logger != null) { if (logger.getName().isEmpty()) { if (root != null) { - throw new IllegalStateException("Configuration has multiple root loggers. There can be only one."); + throw new IllegalStateException( + "Configuration has multiple root loggers. There can be only one."); } root = logger; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResource.java new file mode 100644 index 00000000000..235e8645aa8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResource.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 static java.util.Objects.requireNonNull; + +import java.net.URI; +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; + +/** + * Container for the {@code MonitorResource} element. + */ +@Plugin(name = "MonitorResource", category = Core.CATEGORY_NAME, printObject = true) +public final class MonitorResource { + + private final URI uri; + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builds MonitorResource instances. + */ + public static final class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + @Required(message = "No URI provided") + private URI uri; + + public Builder setUri(final URI uri) { + this.uri = uri; + return this; + } + + @Override + public MonitorResource build() { + return new MonitorResource(uri); + } + } + + private MonitorResource(final URI uri) { + this.uri = requireNonNull(uri, "uri"); + if (!"file".equals(uri.getScheme())) { + final String message = + String.format("Only `file` scheme is supported in monitor resource URIs! Illegal URI: `%s`", uri); + throw new IllegalArgumentException(message); + } + } + + public URI getUri() { + return uri; + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof MonitorResource)) { + return false; + } + final MonitorResource other = (MonitorResource) object; + return this.uri == other.uri; + } + + @Override + public String toString() { + return String.format("MonitorResource{%s}", uri); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResources.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResources.java new file mode 100644 index 00000000000..f7cc5e20260 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorResources.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.config; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; +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 the {@code MonitorResources} element. + */ +@Plugin(name = "MonitorResources", category = Core.CATEGORY_NAME, printObject = true) +public final class MonitorResources { + + private final Set resources; + + private MonitorResources(final Set resources) { + this.resources = requireNonNull(resources, "resources"); + } + + @PluginFactory + public static MonitorResources createMonitorResources( + @PluginElement("monitorResource") final MonitorResource[] resources) { + requireNonNull(resources, "resources"); + final LinkedHashSet distinctResources = + Arrays.stream(resources).collect(Collectors.toCollection(LinkedHashSet::new)); + return new MonitorResources(distinctResources); + } + + public Set getResources() { + return resources; + } +} 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 index c92c9048ab7..96c60b68d42 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import org.apache.logging.log4j.core.config.plugins.util.PluginType; /** @@ -36,7 +35,7 @@ public class Node { */ public static final String CATEGORY = "Core"; - private final Node parent; + private Node parent; private final String name; private String value; private final PluginType type; @@ -44,7 +43,6 @@ public class Node { 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. @@ -77,6 +75,10 @@ public Node(final Node node) { this.object = node.object; } + public void setParent(final Node parent) { + this.parent = parent; + } + public Map getAttributes() { return attributes; } @@ -150,7 +152,8 @@ public String toString() { if (object == null) { return "null"; } - return type.isObjectPrintable() ? object.toString() : - type.getPluginClass().getName() + " with name " + name; + 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/NullConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/NullConfiguration.java index db6f779a450..973a0193ea0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/NullConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/NullConfiguration.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -35,5 +35,4 @@ public NullConfiguration() { final LoggerConfig root = getRootLogger(); root.setLevel(Level.OFF); } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Order.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Order.java index e6c10944855..2421f42021c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Order.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Order.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/OrderComparator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/OrderComparator.java index a0e2b0ec263..24a02633c31 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/OrderComparator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/OrderComparator.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..84676f4ea22 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 @@ -1,40 +1,41 @@ /* * 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.logging.log4j.core.config; -import java.util.HashMap; -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.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.LookupResult; +import org.apache.logging.log4j.core.lookup.PropertiesLookup; import org.apache.logging.log4j.core.lookup.StrLookup; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; /** * Handles properties defined in the configuration. */ -@Plugin(name = "properties", category = Node.CATEGORY, printObject = true) +@Plugin(name = "Properties", category = Node.CATEGORY, printObject = true) public final class PropertiesPlugin { - private PropertiesPlugin() { - } + private static final StrSubstitutor UNESCAPING_SUBSTITUTOR = createUnescapingSubstitutor(); + + private PropertiesPlugin() {} /** * Creates the Properties component. @@ -43,17 +44,63 @@ private PropertiesPlugin() { * @return An Interpolator that includes the configuration properties. */ @PluginFactory - public static StrLookup configureSubstitutor(@PluginElement("Properties") final Property[] properties, - @PluginConfiguration final Configuration config) { - if (properties == null) { - return new Interpolator(config.getProperties()); + public static StrLookup configureSubstitutor( + @PluginElement("Properties") final Property[] properties, @PluginConfiguration final Configuration config) { + // For backwards compatibility, we unescape all escaped lookups when properties are parsed. + // This matches previous behavior for escaped components which were meant to be executed later on. + final Property[] unescapedProperties = new Property[properties == null ? 0 : properties.length]; + for (int i = 0; i < unescapedProperties.length; i++) { + unescapedProperties[i] = unescape(properties[i]); + } + final Interpolator interpolator = new Interpolator( + new PropertiesLookup(unescapedProperties, config.getProperties()), config.getPluginPackages()); + interpolator.setConfiguration(config); + interpolator.setLoggerContext(config.getLoggerContext()); + return interpolator; + } + + private static Property unescape(final Property input) { + return Property.createProperty(input.getName(), unescape(input.getRawValue()), input.getValue()); + } + + // Visible for testing + static String unescape(final String input) { + return UNESCAPING_SUBSTITUTOR.replace(input); + } + + /** + * Creates a new {@link StrSubstitutor} which is configured with no lookups and does not handle + * defaults. This allows it to unescape one level of escaped lookups without any further processing + * or removing replacing {@code ${ctx:foo:-default}} with {@code default}. + */ + private static StrSubstitutor createUnescapingSubstitutor() { + final StrSubstitutor substitutor = new StrSubstitutor(NullLookup.INSTANCE); + substitutor.setValueDelimiter(null); + substitutor.setValueDelimiterMatcher(null); + return substitutor; + } + + private enum NullLookup implements StrLookup { + INSTANCE; + + @Override + public String lookup(final String key) { + return null; + } + + @Override + public String lookup(final LogEvent event, final String key) { + return null; } - final Map map = new HashMap<>(config.getProperties()); - for (final Property prop : properties) { - map.put(prop.getName(), prop.getValue()); + @Override + public LookupResult evaluate(final String key) { + return null; } - return new Interpolator(new MapLookup(map), config.getPluginPackages()); + @Override + public LookupResult evaluate(final LogEvent event, final String key) { + return null; + } } } 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..d465b697b9b 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 @@ -1,45 +1,53 @@ /* * 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.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.PluginConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.config.plugins.PluginValue; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; /** * Represents a key/value pair in the configuration. */ -@Plugin(name = "property", category = Node.CATEGORY, printObject = true) +@Plugin(name = "Property", category = Node.CATEGORY, printObject = true) public final class Property { + /** + * @since 2.11.2 + */ + public static final Property[] EMPTY_ARRAY = {}; + private static final Logger LOGGER = StatusLogger.getLogger(); private final String name; + private final String rawValue; private final String value; private final boolean valueNeedsLookup; - private Property(final String name, final String value) { + private Property(final String name, final String rawValue, final String value) { this.name = name; + this.rawValue = rawValue; this.value = value; this.valueNeedsLookup = value != null && value.contains("${"); } @@ -52,6 +60,14 @@ public String getName() { return name; } + /** + * Returns the original raw property value without substitution. + * @return the raw value of the property, or empty string if it is not set. + */ + public String getRawValue() { + return Objects.toString(rawValue, Strings.EMPTY); + } + /** * Returns the property value. * @return the value of the property. @@ -62,12 +78,24 @@ public String getValue() { /** * Returns {@code true} if the value contains a substitutable property that requires a lookup to be resolved. - * @return {@code true} if the value contains {@code "${"}, {@code false} otherwise + * @return {@code true} if the value contains {@code "${}"}, {@code false} otherwise */ public boolean isValueNeedsLookup() { return valueNeedsLookup; } + /** + * Evaluate this property with the provided substitutor. If {@link #isValueNeedsLookup()} is {@code false}, + * the {@link #getValue() value} is returned, otherwise the {@link #getRawValue() raw value} is evaluated + * with the given substitutor. + */ + public String evaluate(final StrSubstitutor substitutor) { + return valueNeedsLookup + // Unescape the raw value first, handling '$${ctx:foo}' -> '${ctx:foo}' + ? substitutor.replace(PropertiesPlugin.unescape(getRawValue())) + : getValue(); + } + /** * Creates a Property. * @@ -75,14 +103,44 @@ public boolean isValueNeedsLookup() { * @param value The value. * @return A Property. */ + public static Property createProperty(final String name, final String value) { + return createProperty(name, value, value); + } + + /** + * Creates a Property. + * + * @param name The key. + * @param rawValue The value without any substitution applied. + * @param value The value. + * @return A Property. + */ + public static Property createProperty(final String name, final String rawValue, final String value) { + if (name == null) { + throw new IllegalArgumentException("Property name cannot be null"); + } + return new Property(name, rawValue, value); + } + + /** + * Creates a Property. + * + * @param name The key. + * @param rawValue The value without any substitution applied. + * @param configuration configuration used to resolve the property value from the rawValue + * @return A Property. + */ @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"); - } - return new Property(name, value); + @PluginValue(value = "value", substitute = false) final String rawValue, + @PluginConfiguration final Configuration configuration) { + return createProperty( + name, + rawValue, + configuration == null + ? rawValue + : configuration.getStrSubstitutor().replace(rawValue)); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Reconfigurable.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Reconfigurable.java index 10fb876aa91..e4643389f4d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Reconfigurable.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Reconfigurable.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; 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..54ef4380a39 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 @@ -1,79 +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.config; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.Supplier; - -/** - * Interface for objects that know how to ensure delivery of log events to the appropriate appenders, even during and - * after the configuration has been modified while the system is actively used. - */ -public interface ReliabilityStrategy { - - /** - * 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 marker A Marker or null if none is present. - * @param level The event Level. - * @param data The Message. - * @param t A Throwable or null. - */ - 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 event The log event. - */ - void log(Supplier reconfigured, LogEvent event); - - /** - * 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 - */ - LoggerConfig getActiveLoggerConfig(Supplier next); - - /** - * Called after a log event was logged. - */ - void afterLogEvent(); - - /** - * Called before all appenders are stopped. - */ - void beforeStopAppenders(); - - /** - * Called before the configuration is stopped. - * - * @param configuration the configuration that will be stopped - */ - void beforeStopConfiguration(Configuration configuration); - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Supplier; + +/** + * Interface for objects that know how to ensure delivery of log events to the appropriate appenders, even during and + * after the configuration has been modified while the system is actively used. + */ +public interface ReliabilityStrategy { + + /** + * 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 marker A Marker or null if none is present. + * @param level The event Level. + * @param data The Message. + * @param t A Throwable or null. + */ + 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 event The log event. + */ + void log(Supplier reconfigured, LogEvent event); + + /** + * 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 + */ + LoggerConfig getActiveLoggerConfig(Supplier next); + + /** + * Called after a log event was logged. + */ + void afterLogEvent(); + + /** + * Called before all appenders are stopped. + */ + void beforeStopAppenders(); + + /** + * 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..ae25109d3c1 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 @@ -1,32 +1,30 @@ /* * 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.logging.log4j.core.config; +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; /** * Factory for ReliabilityStrategies. */ public final class ReliabilityStrategyFactory { - private ReliabilityStrategyFactory() { - } + private ReliabilityStrategyFactory() {} /** * Returns a new {@code ReliabilityStrategy} instance based on the value of system property @@ -39,15 +37,15 @@ 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", - "AwaitCompletion"); + final String strategy = + PropertiesUtil.getProperties().getStringProperty("log4j.ReliabilityStrategy", "AwaitCompletion"); if ("AwaitCompletion".equals(strategy)) { return new AwaitCompletionReliabilityStrategy(loggerConfig); } @@ -58,12 +56,15 @@ public static ReliabilityStrategy getReliabilityStrategy(final LoggerConfig logg return new LockingReliabilityStrategy(loggerConfig); } try { - final Class cls = LoaderUtil.loadClass(strategy).asSubclass( - ReliabilityStrategy.class); + final Class cls = + Loader.loadClass(strategy).asSubclass(ReliabilityStrategy.class); return cls.getConstructor(LoggerConfig.class).newInstance(loggerConfig); } catch (final Exception dynamicFailed) { - StatusLogger.getLogger().warn( - "Could not create ReliabilityStrategy for '{}', using default AwaitCompletionReliabilityStrategy: {}", strategy, dynamicFailed); + StatusLogger.getLogger() + .warn( + "Could not create ReliabilityStrategy for '{}', using default AwaitCompletionReliabilityStrategy: {}", + strategy, + dynamicFailed); return new AwaitCompletionReliabilityStrategy(loggerConfig); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Scheduled.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Scheduled.java index 42d883ba11d..08b316b1353 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Scheduled.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Scheduled.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config; @@ -28,5 +28,4 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface Scheduled { -} +public @interface Scheduled {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java index 4c3ae89e824..4ad19b6fb0a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java @@ -1,35 +1,40 @@ /* * 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.logging.log4j.core.config; +import java.util.ArrayList; +import java.util.List; +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; import org.apache.logging.log4j.core.script.AbstractScript; +import org.apache.logging.log4j.util.Strings; +import org.jspecify.annotations.NullMarked; /** * A container of Scripts. */ -@Plugin(name = "scripts", category = Core.CATEGORY_NAME) +@NullMarked +@Plugin(name = "Scripts", category = Core.CATEGORY_NAME) public final class ScriptsPlugin { - private ScriptsPlugin() { - } + private ScriptsPlugin() {} /** * Return the array of scripts @@ -38,7 +43,19 @@ private ScriptsPlugin() { */ @PluginFactory public static AbstractScript[] createScripts(@PluginElement("Scripts") final AbstractScript[] scripts) { + Objects.requireNonNull(scripts, "Scripts array cannot be null"); + if (scripts.length == 0) { + return scripts; + } - return scripts; + final List validScripts = new ArrayList<>(scripts.length); + for (final AbstractScript script : scripts) { + if (Strings.isBlank(script.getName())) { + throw new ConfigurationException("A script defined in lacks an explicit 'name' attribute"); + } else { + validScripts.add(script); + } + } + return validScripts.toArray(new AbstractScript[0]); } } 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..8c5aabf600f --- /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..5f45e00110b --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ClassArbiter.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.arbiters; + +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.util.LoaderUtil; + +/** + * Conditional that determines if the specified class is present. + */ +@Plugin( + name = "ClassArbiter", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + printObject = true, + deferChildren = true) +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..55ac121ed30 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/DefaultArbiter.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.config.arbiters; + +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; + +/** + * Default Condition for a Select Condition. + */ +@Plugin( + name = "DefaultArbiter", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + deferChildren = true, + printObject = true) +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/EnvironmentArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/EnvironmentArbiter.java new file mode 100644 index 00000000000..c6acad12b59 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/EnvironmentArbiter.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.config.arbiters; + +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; + +/** + * Condition that determines if the specified environment variable is set. + */ +@Plugin( + name = "EnvironmentArbiter", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + deferChildren = true, + printObject = true) +public class EnvironmentArbiter implements Arbiter { + + private final String propertyName; + private final String propertyValue; + + private EnvironmentArbiter(final String propertyName, final String propertyValue) { + this.propertyName = propertyName; + this.propertyValue = propertyValue; + } + + /** + * Returns true if either the environment variable is defined (it has any value) or the property value + * matches the requested value. + */ + @Override + public boolean isCondition() { + String value = System.getenv(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 EnvironmentArbiter build() { + return new EnvironmentArbiter(propertyName, propertyValue); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiter.java new file mode 100644 index 00000000000..042b36a8146 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ScriptArbiter.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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 javax.script.SimpleBindings; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +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.util.PluginType; +import org.apache.logging.log4j.core.script.AbstractScript; +import org.apache.logging.log4j.core.script.ScriptRef; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Condition that evaluates a script. + */ +@Plugin( + name = "ScriptArbiter", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + deferChildren = true, + printObject = true) +public class ScriptArbiter implements Arbiter { + + private final AbstractScript script; + private final Configuration configuration; + + private ScriptArbiter(final Configuration configuration, final AbstractScript script) { + this.configuration = configuration; + this.script = script; + } + + /** + * Returns the boolean result of the Script. + */ + @Override + public boolean isCondition() { + final SimpleBindings bindings = new SimpleBindings(); + bindings.putAll(configuration.getProperties()); + bindings.put("substitutor", configuration.getStrSubstitutor()); + final Object object = configuration.getScriptManager().execute(script.getId(), bindings); + return Boolean.parseBoolean(object.toString()); + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + @PluginConfiguration + private AbstractConfiguration configuration; + + @PluginNode + private Node node; + + public Builder setConfiguration(final AbstractConfiguration configuration) { + this.configuration = configuration; + return asBuilder(); + } + + public Builder setNode(final Node node) { + this.node = node; + return asBuilder(); + } + + public Builder asBuilder() { + return this; + } + + public ScriptArbiter build() { + AbstractScript script = null; + for (Node child : node.getChildren()) { + final PluginType type = child.getType(); + if (type == null) { + LOGGER.error("Node {} is missing a Plugintype", child.getName()); + continue; + } + if (AbstractScript.class.isAssignableFrom(type.getPluginClass())) { + script = (AbstractScript) configuration.createPluginObject(type, child); + node.getChildren().remove(child); + break; + } + } + + if (script == null) { + LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter"); + return null; + } + if (configuration.getScriptManager() == null) { + LOGGER.error("Script support is not enabled"); + return null; + } + if (script instanceof ScriptRef) { + if (configuration.getScriptManager().getScript(script.getId()) == null) { + LOGGER.error("No script with name {} has been declared.", script.getId()); + return null; + } + } else { + if (!configuration.getScriptManager().addScript(script)) { + return null; + } + } + return new ScriptArbiter(configuration, script); + } + } +} 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..5805117deac --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiter.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.arbiters; + +import java.util.List; +import java.util.Optional; +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; + +/** + * Class Description goes here. + */ +@Plugin( + name = "Select", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + deferChildren = true, + printObject = true) +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 (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..5fd7da38bf2 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiter.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.config.arbiters; + +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; + +/** + * Condition that determines if the specified property is set. + */ +@Plugin( + name = "SystemPropertyArbiter", + category = Node.CATEGORY, + elementType = Arbiter.ELEMENT_TYPE, + deferChildren = true, + printObject = true) +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/arbiters/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/package-info.java new file mode 100644 index 00000000000..c6bc77ffe58 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/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. + */ +@Export +@Version("2.21.0") +package org.apache.logging.log4j.core.config.arbiters; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderComponentBuilder.java index 88d3c93d87d..26da03d3958 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderRefComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderRefComponentBuilder.java index 74f25590698..26953666c27 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderRefComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderRefComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,5 +20,4 @@ * Assembler for constructing AppenderRef Components. * @since 2.4 */ -public interface AppenderRefComponentBuilder extends FilterableComponentBuilder { -} +public interface AppenderRefComponentBuilder extends FilterableComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java index 2832d58644d..d6c5934b2b6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -54,7 +54,6 @@ public Component() { this.value = null; } - public String addAttribute(final String key, final String newValue) { return attributes.put(key, newValue); } @@ -78,4 +77,4 @@ public String getPluginType() { public String getValue() { return value; } - } +} 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..595c9062a5e 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -84,7 +84,7 @@ public interface ComponentBuilder> extends Builder /** * Returns the name of the component, if any. - * @return The components name or null if it doesn't have one. + * @return The component's name or null if it doesn't have one. */ String getName(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CompositeFilterComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CompositeFilterComponentBuilder.java index cc9d97434b0..3c2eb091307 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CompositeFilterComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CompositeFilterComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -21,5 +21,4 @@ * * @since 2.4 */ -public interface CompositeFilterComponentBuilder extends FilterableComponentBuilder { -} +public interface CompositeFilterComponentBuilder extends FilterableComponentBuilder {} 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..5858b7ac431 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 @@ -1,25 +1,24 @@ /* * 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.logging.log4j.core.config.builder.api; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LoggerContext; @@ -34,7 +33,6 @@ */ public interface ConfigurationBuilder extends Builder { - /** * Adds a ScriptComponent. * @param builder The ScriptComponentBuilder with all of its attributes and sub components set. @@ -63,6 +61,16 @@ public interface ConfigurationBuilder extends Builder add(CustomLevelComponentBuilder builder); + /** + * Adds a top level component. + * @param builder The ComponentBuilder with all of its attributes and sub components set. + * @return this builder instance. + * @since 2.25.0 + */ + default ConfigurationBuilder addComponent(ComponentBuilder builder) { + throw new UnsupportedOperationException(); + } + /** * Adds a Filter component. * @param builder the FilterComponentBuilder with all of its attributes and sub components set. @@ -92,7 +100,6 @@ public interface ConfigurationBuilder extends Builder addProperty(String key, String value); - /** * Returns a builder for creating Async Loggers. * @param name The name of the Logger. @@ -109,7 +116,6 @@ public interface ConfigurationBuilder extends Builder extends Builder extends Builder extends Builder extends Builder extends Builder extends Builder setStatusLevel(Level level); - /** * Sets whether the logging should include constructing Plugins. * @param verbosity "disable" will hide messages from plugin construction. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilderFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilderFactory.java index b2ce3218ad9..735c3ce0906 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilderFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilderFactory.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.builder.api; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CustomLevelComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CustomLevelComponentBuilder.java index de9a073a8a4..77a56875cb3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CustomLevelComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CustomLevelComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing CustomLevel Components. * @since 2.4 */ -public interface CustomLevelComponentBuilder extends ComponentBuilder { - -} +public interface CustomLevelComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterComponentBuilder.java index f65bfddde8d..9d3bebb0853 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing Filter Components. * @since 2.4 */ -public interface FilterComponentBuilder extends ComponentBuilder { - -} +public interface FilterComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterableComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterableComponentBuilder.java index 51b520f2f9b..0bea40df8ad 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterableComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/FilterableComponentBuilder.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.builder.api; /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/KeyValuePairComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/KeyValuePairComponentBuilder.java index d836a94f9f8..9724e9d56ee 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/KeyValuePairComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/KeyValuePairComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing KeyValuePair Components. * @since 2.9 */ -public interface KeyValuePairComponentBuilder extends ComponentBuilder { - -} +public interface KeyValuePairComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LayoutComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LayoutComponentBuilder.java index e2eb7b5f46e..443aa143f72 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LayoutComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LayoutComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing Layout Components. * @since 2.4 */ -public interface LayoutComponentBuilder extends ComponentBuilder { - -} +public interface LayoutComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggableComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggableComponentBuilder.java index 497b3c61ae1..d8dfd700e3a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggableComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggableComponentBuilder.java @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.builder.api; /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggerComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggerComponentBuilder.java index b371e5597f6..c5e94b76264 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggerComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggerComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,5 +20,4 @@ * Assembler for constructing Logger Components. * @since 2.4 */ -public interface LoggerComponentBuilder extends LoggableComponentBuilder { -} +public interface LoggerComponentBuilder extends LoggableComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/PropertyComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/PropertyComponentBuilder.java index bbf39951c26..38bddb60358 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/PropertyComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/PropertyComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing Property Components. * @since 2.9 */ -public interface PropertyComponentBuilder extends ComponentBuilder { - -} +public interface PropertyComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/RootLoggerComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/RootLoggerComponentBuilder.java index 1f71d2a7a4e..045aaa8dac4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/RootLoggerComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/RootLoggerComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,5 +20,4 @@ * Assembler for constructing the root Logger Components. * @since 2.4 */ -public interface RootLoggerComponentBuilder extends LoggableComponentBuilder { -} +public interface RootLoggerComponentBuilder extends LoggableComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptComponentBuilder.java index a8de90fd16b..f48910bdb68 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; @@ -20,6 +20,4 @@ * Assembler for constructing Layout Components. * @since 2.5 */ -public interface ScriptComponentBuilder extends ComponentBuilder { - -} +public interface ScriptComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptFileComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptFileComponentBuilder.java index 43fc6c243d0..fffc91744e3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptFileComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ScriptFileComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.api; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java index 3fa9b4c0466..6d91c2d89d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java @@ -19,4 +19,9 @@ * * @since 2.4 */ +@Export +@Version("2.25.0") package org.apache.logging.log4j.core.config.builder.api; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java index ae932eef6c0..8445dc706a4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java @@ -1,39 +1,34 @@ /* * 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.logging.log4j.core.config.builder.impl; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; 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.builder.api.Component; 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.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; /** @@ -43,7 +38,6 @@ * @since 2.4 */ public class BuiltConfiguration extends AbstractConfiguration { - private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() }; private final StatusConfiguration statusConfig; protected Component rootComponent; private Component loggersComponent; @@ -52,11 +46,14 @@ public class BuiltConfiguration extends AbstractConfiguration { private Component propertiesComponent; private Component customLevelsComponent; private Component scriptsComponent; + private Component monitorResourcesComponent; + private Component asyncWaitStrategyFactoryComponent; private String contentType = "text"; - public BuiltConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, final Component rootComponent) { + public BuiltConfiguration( + final LoggerContext loggerContext, final ConfigurationSource source, final Component rootComponent) { super(loggerContext, source); - statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES).withStatus(getDefaultStatus()); + statusConfig = new StatusConfiguration().withStatus(getDefaultStatus()); for (final Component component : rootComponent.getComponents()) { switch (component.getPluginType()) { case "Scripts": { @@ -83,6 +80,14 @@ public BuiltConfiguration(final LoggerContext loggerContext, final Configuration customLevelsComponent = component; break; } + case "MonitorResources": { + monitorResourcesComponent = component; + break; + } + case "AsyncWaitStrategyFactory": { + asyncWaitStrategyFactoryComponent = component; + break; + } } } this.rootComponent = rootComponent; @@ -100,11 +105,19 @@ public void setup() { if (customLevelsComponent.getComponents().size() > 0) { children.add(convertToNode(rootNode, customLevelsComponent)); } + if (monitorResourcesComponent != null + && monitorResourcesComponent.getComponents().size() > 0) { + children.add(convertToNode(rootNode, monitorResourcesComponent)); + } + if (asyncWaitStrategyFactoryComponent != null) { + children.add(convertToNode(rootNode, asyncWaitStrategyFactoryComponent)); + } children.add(convertToNode(rootNode, loggersComponent)); children.add(convertToNode(rootNode, appendersComponent)); if (filtersComponent.getComponents().size() > 0) { if (filtersComponent.getComponents().size() == 1) { - children.add(convertToNode(rootNode, filtersComponent.getComponents().get(0))); + children.add( + convertToNode(rootNode, filtersComponent.getComponents().get(0))); } else { children.add(convertToNode(rootNode, filtersComponent)); } @@ -153,17 +166,7 @@ public void setShutdownTimeoutMillis(final long shutdownTimeoutMillis) { public void setMonitorInterval(final int intervalSeconds) { if (this instanceof Reconfigurable && intervalSeconds > 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); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderComponentBuilder.java index fc06bf4b6cd..6a936730e11 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -29,8 +29,8 @@ class DefaultAppenderComponentBuilder extends DefaultComponentAndConfigurationBuilder implements AppenderComponentBuilder { - public DefaultAppenderComponentBuilder(final DefaultConfigurationBuilder builder, final String name, - final String type) { + public DefaultAppenderComponentBuilder( + final DefaultConfigurationBuilder builder, final String name, final String type) { super(builder, name, type); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderRefComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderRefComponentBuilder.java index 0bc766faedf..d63cfc4bec5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderRefComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderRefComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -28,13 +28,12 @@ class DefaultAppenderRefComponentBuilder extends DefaultComponentAndConfigurationBuilder implements AppenderRefComponentBuilder { - public DefaultAppenderRefComponentBuilder(final DefaultConfigurationBuilder builder, - final String ref) { + public DefaultAppenderRefComponentBuilder( + final DefaultConfigurationBuilder builder, final String ref) { super(builder, "AppenderRef"); addAttribute("ref", ref); } - @Override public AppenderRefComponentBuilder add(final FilterComponentBuilder builder) { return addComponent(builder); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java index f6e7e1e6bbe..8b2b46aafd1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentAndConfigurationBuilder.java @@ -1,47 +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.builder.impl; - -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; - -/** - * Extends {@code DefaultComponentBuilder} to specify - * {@code DefaultConfigurationBuilder} as the - * {@code ConfigurationBuilder} type. - * - * @since 2.4 - */ -class DefaultComponentAndConfigurationBuilder> - extends DefaultComponentBuilder> { - - DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, - final String type, final String value) { - super(builder, name, type, value); - } - - DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, final String name, - final String type) { - super(builder, name, type); - } - - public DefaultComponentAndConfigurationBuilder(final DefaultConfigurationBuilder builder, - final String type) { - super(builder, type); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.builder.impl; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; + +/** + * Extends {@code DefaultComponentBuilder} to specify + * {@code DefaultConfigurationBuilder} as the + * {@code ConfigurationBuilder} type. + * + * @since 2.4 + */ +class DefaultComponentAndConfigurationBuilder> + extends DefaultComponentBuilder> { + + DefaultComponentAndConfigurationBuilder( + final DefaultConfigurationBuilder builder, + final String name, + final String type, + final String value) { + super(builder, name, type, value); + } + + DefaultComponentAndConfigurationBuilder( + final DefaultConfigurationBuilder builder, final String name, final String type) { + super(builder, name, type); + } + + public DefaultComponentAndConfigurationBuilder( + final DefaultConfigurationBuilder builder, final String type) { + super(builder, type); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java index b73039dd123..2983a243acc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -20,7 +20,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.builder.api.Component; @@ -51,8 +50,7 @@ public DefaultComponentBuilder(final CB builder, final String name, final String this(builder, name, type, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type, - final String value) { + public DefaultComponentBuilder(final CB builder, final String name, final String type, final String value) { this.type = type; this.builder = builder; this.name = name; @@ -74,7 +72,6 @@ public T addAttribute(final String key, final int value) { return put(key, Integer.toString(value)); } - @Override public T addAttribute(final String key, final Level level) { return put(key, level.toString()); @@ -85,7 +82,6 @@ public T addAttribute(final String key, final Object value) { return put(key, value.toString()); } - @Override public T addAttribute(final String key, final String value) { return put(key, value); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCompositeFilterComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCompositeFilterComponentBuilder.java index 86ccd2b55ad..359644eb8de 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCompositeFilterComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCompositeFilterComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -28,8 +28,10 @@ class DefaultCompositeFilterComponentBuilder extends DefaultComponentAndConfigurationBuilder implements CompositeFilterComponentBuilder { - public DefaultCompositeFilterComponentBuilder(final DefaultConfigurationBuilder builder, - final String onMatch, final String onMismatch) { + public DefaultCompositeFilterComponentBuilder( + final DefaultConfigurationBuilder builder, + final String onMatch, + final String onMismatch) { super(builder, "Filters"); addAttribute(AbstractFilterBuilder.ATTR_ON_MATCH, onMatch); addAttribute(AbstractFilterBuilder.ATTR_ON_MISMATCH, onMismatch); @@ -39,5 +41,4 @@ public DefaultCompositeFilterComponentBuilder(final DefaultConfigurationBuilder< public CompositeFilterComponentBuilder add(final FilterComponentBuilder builder) { return addComponent(builder); } - } 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..bf039a2dafc 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 @@ -1,32 +1,44 @@ /* * 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.logging.log4j.core.config.builder.impl; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; 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; import org.apache.logging.log4j.core.LoggerContext; @@ -48,6 +60,7 @@ import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; 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.util.Integers; import org.apache.logging.log4j.core.util.Throwables; /** @@ -57,8 +70,7 @@ 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; @@ -70,7 +82,6 @@ public class DefaultConfigurationBuilder implement private ConfigurationSource source; private int monitorInterval; private Level level; - private String verbosity; private String destination; private String packages; private String shutdownFlag; @@ -79,6 +90,17 @@ public class DefaultConfigurationBuilder implement private LoggerContext loggerContext; private String name; + @SuppressFBWarnings( + value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"}, + justification = "This method only uses internally generated data.") + 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); @@ -115,6 +137,17 @@ public ConfigurationBuilder add(final AppenderComponentBuilder builder) { return add(appenders, builder); } + @Override + public ConfigurationBuilder addComponent(ComponentBuilder builder) { + return add(root, builder); + } + + private Optional getTopLevelComponent(final String pluginType) { + return root.getComponents().stream() + .filter(component -> component.getPluginType().equals(pluginType)) + .findAny(); + } + @Override public ConfigurationBuilder add(final CustomLevelComponentBuilder builder) { return add(customLevels, builder); @@ -168,9 +201,9 @@ public T build(final boolean initialize) { if (source == null) { source = ConfigurationSource.NULL_SOURCE; } - final Constructor constructor = clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class); + 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); @@ -178,9 +211,6 @@ public T build(final boolean initialize) { if (level != null) { configuration.getStatusConfiguration().withStatus(level); } - if (verbosity != null) { - configuration.getStatusConfiguration().withVerbosity(verbosity); - } if (destination != null) { configuration.getStatusConfiguration().withDestination(destination); } @@ -196,6 +226,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); } @@ -206,6 +237,13 @@ public T build(final boolean initialize) { return configuration; } + 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(); + } + @Override public void writeXmlConfiguration(final OutputStream output) throws IOException { try { @@ -214,7 +252,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,21 +260,20 @@ 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 void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException { xmlWriter.writeStartDocument(); - xmlWriter.writeCharacters(EOL); - xmlWriter.writeStartElement("Configuration"); if (name != null) { xmlWriter.writeAttribute("name", name); @@ -244,9 +281,6 @@ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLSt if (level != null) { xmlWriter.writeAttribute("status", level.name()); } - if (verbosity != null) { - xmlWriter.writeAttribute("verbose", verbosity); - } if (destination != null) { xmlWriter.writeAttribute("dest", destination); } @@ -266,13 +300,20 @@ 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); + Optional monitorResourcesComponent = getTopLevelComponent("MonitorResources"); + if (monitorResourcesComponent.isPresent() + && !monitorResourcesComponent.get().getComponents().isEmpty()) { + writeXmlComponent(xmlWriter, monitorResourcesComponent.get()); + } + Optional asyncWaitStrategyFactoryComponent = getTopLevelComponent("AsyncWaitStrategyFactory"); + if (asyncWaitStrategyFactoryComponent.isPresent()) { + writeXmlComponent(xmlWriter, asyncWaitStrategyFactoryComponent.get()); + } 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,61 +321,47 @@ 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); + if (!component.getAttributes().isEmpty() + || !component.getComponents().isEmpty() + || component.getValue() != null) { + 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 { - for (final Map.Entry attribute : component.getAttributes().entrySet()) { + private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component) + throws XMLStreamException { + for (final Map.Entry attribute : + component.getAttributes().entrySet()) { xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue()); } } - @Override public ScriptComponentBuilder newScript(final String name, final String language, final String text) { return new DefaultScriptComponentBuilder(this, name, language, text); } - @Override public ScriptFileComponentBuilder newScriptFile(final String path) { return new DefaultScriptFileComponentBuilder(this, path, path); @@ -356,12 +383,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 +409,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 +418,7 @@ public RootLoggerComponentBuilder newAsyncRootLogger() { } @Override - public RootLoggerComponentBuilder newAsyncRootLogger(boolean includeLocation) { + public RootLoggerComponentBuilder newAsyncRootLogger(final boolean includeLocation) { return new DefaultRootLoggerComponentBuilder(this, null, "AsyncRoot", includeLocation); } @@ -415,7 +442,6 @@ public RootLoggerComponentBuilder newAsyncRootLogger(final String level, final b return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot", includeLocation); } - @Override public > ComponentBuilder newComponent(final String type) { return new DefaultComponentBuilder<>(this, type); @@ -427,8 +453,8 @@ public > ComponentBuilder newComponent(final St } @Override - public > ComponentBuilder newComponent(final String name, final String type, - final String value) { + public > ComponentBuilder newComponent( + final String name, final String type, final String value) { return new DefaultComponentBuilder<>(this, name, type, value); } @@ -448,8 +474,8 @@ public CustomLevelComponentBuilder newCustomLevel(final String name, final int l } @Override - public FilterComponentBuilder newFilter(final String type, final Filter.Result onMatch, - final Filter.Result onMismatch) { + public FilterComponentBuilder newFilter( + final String type, final Filter.Result onMatch, final Filter.Result onMismatch) { return new DefaultFilterComponentBuilder(this, type, onMatch.name(), onMismatch.name()); } @@ -464,12 +490,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 +525,7 @@ public RootLoggerComponentBuilder newRootLogger() { } @Override - public RootLoggerComponentBuilder newRootLogger(boolean includeLocation) { + public RootLoggerComponentBuilder newRootLogger(final boolean includeLocation) { return new DefaultRootLoggerComponentBuilder(this, null, includeLocation); } @@ -555,7 +581,7 @@ public ConfigurationBuilder setConfigurationSource(final ConfigurationSource @Override public ConfigurationBuilder setMonitorInterval(final String intervalSeconds) { - monitorInterval = Integer.parseInt(intervalSeconds); + monitorInterval = Integers.parseInt(intervalSeconds); return this; } @@ -583,9 +609,12 @@ public ConfigurationBuilder setStatusLevel(final Level level) { return this; } + /** + * @deprecated This method is ineffective and only kept for binary backward compatibility. + */ @Override + @Deprecated public ConfigurationBuilder setVerbosity(final String verbosity) { - this.verbosity = verbosity; return this; } @@ -605,5 +634,4 @@ public ConfigurationBuilder addRootProperty(final String key, final String va root.getAttributes().put(key, value); return this; } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCustomLevelComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCustomLevelComponentBuilder.java index d3c86f590a5..12f0b2b022a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCustomLevelComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultCustomLevelComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -25,8 +25,8 @@ class DefaultCustomLevelComponentBuilder extends DefaultComponentAndConfigurationBuilder implements CustomLevelComponentBuilder { - public DefaultCustomLevelComponentBuilder(final DefaultConfigurationBuilder builder, - final String name, final int level) { + public DefaultCustomLevelComponentBuilder( + final DefaultConfigurationBuilder builder, final String name, final int level) { super(builder, name, "CustomLevel"); addAttribute("intLevel", level); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultFilterComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultFilterComponentBuilder.java index 89b69f66ed5..8a7acab7379 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultFilterComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultFilterComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -26,8 +26,11 @@ class DefaultFilterComponentBuilder extends DefaultComponentAndConfigurationBuilder implements FilterComponentBuilder { - public DefaultFilterComponentBuilder(final DefaultConfigurationBuilder builder, final String type, - final String onMatch, final String onMismatch) { + public DefaultFilterComponentBuilder( + final DefaultConfigurationBuilder builder, + final String type, + final String onMatch, + final String onMismatch) { super(builder, type); addAttribute(AbstractFilterBuilder.ATTR_ON_MATCH, onMatch); addAttribute(AbstractFilterBuilder.ATTR_ON_MISMATCH, onMismatch); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultKeyValuePairComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultKeyValuePairComponentBuilder.java index a21e0107b25..9394232a717 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultKeyValuePairComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultKeyValuePairComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -25,9 +25,9 @@ class DefaultKeyValuePairComponentBuilder extends DefaultComponentAndConfigurationBuilder implements KeyValuePairComponentBuilder { - public DefaultKeyValuePairComponentBuilder(final DefaultConfigurationBuilder builder, - final String key, final String value) { - super(builder,"KeyValuePair"); + public DefaultKeyValuePairComponentBuilder( + final DefaultConfigurationBuilder builder, final String key, final String value) { + super(builder, "KeyValuePair"); addAttribute("key", key); addAttribute("value", value); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLayoutComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLayoutComponentBuilder.java index cf827cc904c..fcfe497ba36 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLayoutComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLayoutComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -25,7 +25,8 @@ class DefaultLayoutComponentBuilder extends DefaultComponentAndConfigurationBuilder implements LayoutComponentBuilder { - public DefaultLayoutComponentBuilder(final DefaultConfigurationBuilder builder, final String type) { + public DefaultLayoutComponentBuilder( + final DefaultConfigurationBuilder builder, final String type) { super(builder, type); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLoggerComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLoggerComponentBuilder.java index b982a9ac7f0..a7cab3d22c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLoggerComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultLoggerComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -33,8 +33,8 @@ class DefaultLoggerComponentBuilder extends DefaultComponentAndConfigurationBuil * @param name * @param level */ - public DefaultLoggerComponentBuilder(final DefaultConfigurationBuilder builder, final String name, - final String level) { + public DefaultLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, final String name, final String level) { super(builder, name, "Logger"); if (level != null) { addAttribute("level", level); @@ -48,8 +48,11 @@ public DefaultLoggerComponentBuilder(final DefaultConfigurationBuilder builder, final String name, - final String level, final boolean includeLocation) { + public DefaultLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, + final String name, + final String level, + final boolean includeLocation) { super(builder, name, "Logger"); if (level != null) { addAttribute("level", level); @@ -64,8 +67,11 @@ public DefaultLoggerComponentBuilder(final DefaultConfigurationBuilder builder, final String name, - final String level, final String type) { + public DefaultLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, + final String name, + final String level, + final String type) { super(builder, name, type); if (level != null) { addAttribute("level", level); @@ -80,8 +86,12 @@ public DefaultLoggerComponentBuilder(final DefaultConfigurationBuilder builder, final String name, - final String level, final String type, final boolean includeLocation) { + public DefaultLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, + final String name, + final String level, + final String type, + final boolean includeLocation) { super(builder, name, type); if (level != null) { addAttribute("level", level); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultPropertyComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultPropertyComponentBuilder.java index cc81a53fcf5..aeba78fb187 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultPropertyComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultPropertyComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -25,8 +25,8 @@ class DefaultPropertyComponentBuilder extends DefaultComponentAndConfigurationBuilder implements PropertyComponentBuilder { - public DefaultPropertyComponentBuilder(final DefaultConfigurationBuilder builder, - final String name, final String value) { + public DefaultPropertyComponentBuilder( + final DefaultConfigurationBuilder builder, final String name, final String value) { super(builder, name, "Property", value); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultRootLoggerComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultRootLoggerComponentBuilder.java index 76b112fb589..c33f2e3fdfe 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultRootLoggerComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultRootLoggerComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -32,8 +32,8 @@ class DefaultRootLoggerComponentBuilder extends DefaultComponentAndConfiguration * @param builder * @param level */ - public DefaultRootLoggerComponentBuilder(final DefaultConfigurationBuilder builder, - final String level) { + public DefaultRootLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, final String level) { super(builder, "", "Root"); if (level != null) { addAttribute("level", level); @@ -46,8 +46,10 @@ public DefaultRootLoggerComponentBuilder(final DefaultConfigurationBuilder builder, - final String level, final boolean includeLocation) { + public DefaultRootLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, + final String level, + final boolean includeLocation) { super(builder, "", "Root"); if (level != null) { addAttribute("level", level); @@ -61,23 +63,25 @@ public DefaultRootLoggerComponentBuilder(final DefaultConfigurationBuilder builder, - final String level, final String type) { + public DefaultRootLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, final String level, final String type) { super(builder, "", type); if (level != null) { addAttribute("level", level); } } - /** * Configure the root logger. * @param builder * @param level * @param type */ - public DefaultRootLoggerComponentBuilder(final DefaultConfigurationBuilder builder, - final String level, final String type, final boolean includeLocation) { + public DefaultRootLoggerComponentBuilder( + final DefaultConfigurationBuilder builder, + final String level, + final String type, + final boolean includeLocation) { super(builder, "", type); if (level != null) { addAttribute("level", level); @@ -90,7 +94,6 @@ public RootLoggerComponentBuilder add(final AppenderRefComponentBuilder builder) return addComponent(builder); } - @Override public RootLoggerComponentBuilder add(final FilterComponentBuilder builder) { return addComponent(builder); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptComponentBuilder.java index d79f0cd379a..ebcaa7920ba 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -25,8 +25,11 @@ class DefaultScriptComponentBuilder extends DefaultComponentAndConfigurationBuilder implements ScriptComponentBuilder { - public DefaultScriptComponentBuilder(final DefaultConfigurationBuilder builder, - final String name, final String language, final String text) { + public DefaultScriptComponentBuilder( + final DefaultConfigurationBuilder builder, + final String name, + final String language, + final String text) { super(builder, name, "Script"); if (language != null) { addAttribute("language", language); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptFileComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptFileComponentBuilder.java index c9d50efe1c3..ced65e9cd99 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptFileComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultScriptFileComponentBuilder.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.builder.impl; @@ -27,8 +27,8 @@ class DefaultScriptFileComponentBuilder extends DefaultComponentAndConfigurationBuilder implements ScriptFileComponentBuilder { - public DefaultScriptFileComponentBuilder(final DefaultConfigurationBuilder builder, - final String name, final String path) { + public DefaultScriptFileComponentBuilder( + final DefaultConfigurationBuilder builder, final String name, final String path) { super(builder, name != null ? name : path, "ScriptFile"); addAttribute("path", path); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java index 07ed7a586d8..32a69487d75 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java @@ -19,4 +19,9 @@ * * @since 2.4 */ +@Export +@Version("2.25.0") package org.apache.logging.log4j.core.config.builder.impl; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..bf859b1411e 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 @@ -1,43 +1,42 @@ /* * 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.logging.log4j.core.config.composite; -import java.io.File; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.AbstractConfiguration; 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.Loader; 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.core.util.Watcher; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -50,47 +49,45 @@ public class CompositeConfiguration extends AbstractConfiguration implements Rec */ 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; /** - * 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()); + 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 = Loader.newInstanceOf(mergeStrategyClassName); + } catch (ClassNotFoundException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException + | InstantiationException ex) { mergeStrategy = new DefaultMergeStrategy(); } for (final AbstractConfiguration config : configurations) { mergeStrategy.mergeRootProperties(rootNode, config); } - final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) - .withStatus(getDefaultStatus()); + final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(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.withStatus(toRootUpperCase(value)); } else if ("dest".equalsIgnoreCase(key)) { statusConfig.withDestination(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); } else if ("packages".equalsIgnoreCase(key)) { pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); } else if ("name".equalsIgnoreCase(key)) { @@ -106,14 +103,14 @@ 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())) { @@ -132,11 +129,13 @@ 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())); } } } @@ -152,7 +151,8 @@ public Configuration reconfigure() { final URI sourceURI = source.getURI(); Configuration currentConfig = config; if (sourceURI == null) { - LOGGER.warn("Unable to determine URI for configuration {}, changes to it will be ignored", + LOGGER.warn( + "Unable to determine URI for configuration {}, changes to it will be ignored", config.getName()); } else { currentConfig = factory.getConfiguration(getLoggerContext(), config.getName(), sourceURI); @@ -161,7 +161,6 @@ public Configuration reconfigure() { } } configs.add((AbstractConfiguration) currentConfig); - } return new CompositeConfiguration(configs); @@ -174,7 +173,11 @@ private void staffChildConfiguration(final AbstractConfiguration childConfigurat } private void printNodes(final String indent, final Node node, final StringBuilder sb) { - sb.append(indent).append(node.getName()).append(" type: ").append(node.getType()).append("\n"); + sb.append(indent) + .append(node.getName()) + .append(" type: ") + .append(node.getType()) + .append("\n"); sb.append(indent).append(node.getAttributes().toString()).append("\n"); for (final Node child : node.getChildren()) { printNodes(indent + " ", child, sb); 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..1003918eb47 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 @@ -1,26 +1,28 @@ /* * 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.logging.log4j.core.config.composite; +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; +import static org.apache.logging.log4j.util.Strings.toRootUpperCase; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -28,6 +30,7 @@ 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.core.util.Integers; /** * The default merge strategy for composite configurations. @@ -41,7 +44,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.
  • @@ -70,31 +73,40 @@ public class DefaultMergeStrategy implements MergeStrategy { */ @Override public void mergeRootProperties(final Node rootNode, final AbstractConfiguration configuration) { - for (final Map.Entry attribute : configuration.getRootNode().getAttributes().entrySet()) { + for (final Map.Entry attribute : + configuration.getRootNode().getAttributes().entrySet()) { boolean isFound = false; - for (final Map.Entry targetAttribute : rootNode.getAttributes().entrySet()) { + for (final Map.Entry targetAttribute : + rootNode.getAttributes().entrySet()) { if (targetAttribute.getKey().equalsIgnoreCase(attribute.getKey())) { if (attribute.getKey().equalsIgnoreCase(STATUS)) { - final Level targetLevel = Level.getLevel(targetAttribute.getValue().toUpperCase()); - final Level sourceLevel = Level.getLevel(attribute.getValue().toUpperCase()); + final Level targetLevel = Level.getLevel(toRootUpperCase(targetAttribute.getValue())); + final Level sourceLevel = Level.getLevel(toRootUpperCase(attribute.getValue())); if (targetLevel != null && sourceLevel != null) { if (sourceLevel.isLessSpecificThan(targetLevel)) { targetAttribute.setValue(attribute.getValue()); } - } else - if (sourceLevel != null) { - targetAttribute.setValue(attribute.getValue()); - } - } else { - if (attribute.getKey().equalsIgnoreCase("monitorInterval")) { - final int sourceInterval = Integer.parseInt(attribute.getValue()); - final int targetInterval = Integer.parseInt(targetAttribute.getValue()); - if (targetInterval == 0 || sourceInterval < targetInterval) { - targetAttribute.setValue(attribute.getValue()); - } - } else { + } else if (sourceLevel != null) { targetAttribute.setValue(attribute.getValue()); } + } else if (attribute.getKey().equalsIgnoreCase("monitorInterval")) { + final int sourceInterval = Integers.parseInt(attribute.getValue()); + final int targetInterval = Integers.parseInt(targetAttribute.getValue()); + if (targetInterval == 0 || sourceInterval < targetInterval) { + targetAttribute.setValue(attribute.getValue()); + } + } else if (attribute.getKey().equalsIgnoreCase("packages")) { + final String sourcePackages = attribute.getValue(); + final String targetPackages = targetAttribute.getValue(); + if (sourcePackages != null) { + if (targetPackages != null) { + targetAttribute.setValue(targetPackages + "," + sourcePackages); + } else { + targetAttribute.setValue(sourcePackages); + } + } + } else { + targetAttribute.setValue(attribute.getValue()); } isFound = true; } @@ -131,13 +143,15 @@ public void mergConfigurations(final Node target, final Node source, final Plugi continue; } - switch (targetChildNode.getName().toLowerCase()) { + switch (toRootLowerCase(targetChildNode.getName())) { case PROPERTIES: case SCRIPTS: case APPENDERS: { for (final Node node : sourceChildNode.getChildren()) { for (final Node targetNode : targetChildNode.getChildren()) { - if (Objects.equals(targetNode.getAttributes().get(NAME), node.getAttributes().get(NAME))) { + if (Objects.equals( + targetNode.getAttributes().get(NAME), + node.getAttributes().get(NAME))) { targetChildNode.getChildren().remove(targetNode); break; } @@ -153,7 +167,8 @@ public void mergConfigurations(final Node target, final Node source, final Plugi targetLoggers.put(node.getName(), node); } for (final Node node : sourceChildNode.getChildren()) { - final Node targetNode = getLoggerNode(targetChildNode, node.getAttributes().get(NAME)); + final Node targetNode = getLoggerNode( + targetChildNode, node.getAttributes().get(NAME)); final Node loggerNode = new Node(targetChildNode, node.getName(), node.getType()); if (targetNode != null) { targetNode.getAttributes().putAll(node.getAttributes()); @@ -162,22 +177,24 @@ public void mergConfigurations(final Node target, final Node source, final Plugi boolean foundFilter = false; for (final Node targetChild : targetNode.getChildren()) { if (isFilterNode(targetChild)) { - updateFilterNode(loggerNode, targetChild, sourceLoggerChild, - pluginManager); + updateFilterNode( + loggerNode, targetChild, sourceLoggerChild, pluginManager); foundFilter = true; break; } } if (!foundFilter) { - final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), + final Node childNode = new Node( + loggerNode, + sourceLoggerChild.getName(), sourceLoggerChild.getType()); childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); childNode.getChildren().addAll(sourceLoggerChild.getChildren()); targetNode.getChildren().add(childNode); } } else { - final Node childNode = new Node(loggerNode, sourceLoggerChild.getName(), - sourceLoggerChild.getType()); + final Node childNode = new Node( + loggerNode, sourceLoggerChild.getName(), sourceLoggerChild.getType()); childNode.getAttributes().putAll(sourceLoggerChild.getAttributes()); childNode.getChildren().addAll(sourceLoggerChild.getChildren()); if (childNode.getName().equalsIgnoreCase("AppenderRef")) { @@ -213,7 +230,6 @@ public void mergConfigurations(final Node target, final Node source, final Plugi isMerged = true; break; } - } } if (!isMerged) { @@ -239,7 +255,10 @@ private Node getLoggerNode(final Node parentNode, final String name) { return null; } - private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode, + private void updateFilterNode( + final Node target, + final Node targetChildNode, + final Node sourceChildNode, final PluginManager pluginManager) { if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) { final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType()); @@ -266,11 +285,15 @@ 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 + && toRootLowerCase(value) + .equals(toRootLowerCase(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 + && toRootLowerCase(value) + .equals(toRootLowerCase(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..7d9aee400d4 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.composite; 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..787cf180434 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,9 @@ /** * Support for composite configurations. */ -package org.apache.logging.log4j.core.config.composite; \ No newline at end of file +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.config.composite; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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..b574070a433 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 @@ -1,21 +1,24 @@ /* * 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.logging.log4j.core.config.json; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -25,22 +28,16 @@ import java.util.Iterator; 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.Integers; import org.apache.logging.log4j.core.util.Patterns; /** @@ -48,7 +45,6 @@ */ public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable { - private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() }; private final List status = new ArrayList<>(); private JsonNode root; @@ -68,11 +64,12 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS } } processAttributes(rootNode, root); - final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) - .withStatus(getDefaultStatus()); - for (final Map.Entry entry : rootNode.getAttributes().entrySet()) { + final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(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); @@ -82,25 +79,17 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); } else if ("shutdownTimeout".equalsIgnoreCase(key)) { shutdownTimeoutMillis = Long.parseLong(value); - } else if ("verbose".equalsIgnoreCase(entry.getKey())) { - statusConfig.withVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); } 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 = Integers.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()); @@ -175,7 +164,8 @@ private Node constructNode(final String name, final Node parent, final JsonNode } else { LOGGER.debug("Processing {} {}[{}]", pluginType, entry.getKey(), i); } - final Iterator> itemIter = n.get(i).fields(); + final Iterator> itemIter = + n.get(i).fields(); final List itemChildren = item.getChildren(); while (itemIter.hasNext()) { final Map.Entry itemEntry = itemIter.next(); @@ -190,7 +180,6 @@ private Node constructNode(final String name, final Node parent, final JsonNode itemChildren.add(constructNode(entryName, item, array.get(j))); } } - } children.add(item); } @@ -210,8 +199,11 @@ private Node constructNode(final String name, final Node parent, final JsonNode t = type.getElementName() + ':' + type.getPluginClass(); } - final String p = node.getParent() == null ? "null" - : node.getParent().getName() == null ? LoggerConfig.ROOT : node.getParent().getName(); + 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; } 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..ddcc61b4a60 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.json; @@ -34,9 +34,9 @@ public class JsonConfigurationFactory extends ConfigurationFactory { private static final String[] SUFFIXES = new String[] {".json", ".jsn"}; private static final String[] dependencies = new String[] { - "com.fasterxml.jackson.databind.ObjectMapper", - "com.fasterxml.jackson.databind.JsonNode", - "com.fasterxml.jackson.core.JsonParser" + "com.fasterxml.jackson.databind.ObjectMapper", + "com.fasterxml.jackson.databind.JsonNode", + "com.fasterxml.jackson.core.JsonParser" }; private final boolean isActive; @@ -44,7 +44,9 @@ public class JsonConfigurationFactory extends ConfigurationFactory { public JsonConfigurationFactory() { for (final String dependency : dependencies) { if (!Loader.isClassAvailable(dependency)) { - LOGGER.debug("Missing dependencies for Json support, ConfigurationFactory {} is inactive", getClass().getName()); + LOGGER.debug( + "Missing dependencies for Json support, ConfigurationFactory {} is inactive", + getClass().getName()); isActive = false; return; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java index cd3dfe738fe..9d9e5d60eb3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java @@ -17,4 +17,9 @@ /** * Classes and interfaces supporting configuration of Log4j 2 with JSON. */ +@Export +@Version("2.20.1") package org.apache.logging.log4j.core.config.json; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java index 169f01a0742..b0dbdc1ea00 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java @@ -17,4 +17,9 @@ /** * Configuration of Log4j 2. */ +@Export +@Version("2.26.0") package org.apache.logging.log4j.core.config; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; 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 index 8aaf11753b3..0de87263dc5 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.apache.logging.log4j.util.Strings; /** 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..48dd2990d19 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; 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..42b3605f6ab 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ 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; @@ -99,5 +98,4 @@ * be output as a hashed value. */ boolean sensitive() default false; - } 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..142ae6de921 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.plugins; import java.lang.annotation.Documented; @@ -22,7 +21,6 @@ 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; 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..c9a887d83a0 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.plugins; import java.lang.annotation.Documented; 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..da2fee48362 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ 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; /** 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..a01ffe790e8 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ 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; /** 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..375d601a349 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; 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..e01711e155a --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginLoggerContext.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.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.visitors.PluginLoggerContextVisitor; + +/** + * Identifies a parameter or field as a LoggerContext. + * @see org.apache.logging.log4j.core.LoggerContext + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@PluginVisitorStrategy(PluginLoggerContextVisitor.class) +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..19177c4501e 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ 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; /** 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..8ad05bbcf1f 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins; @@ -21,7 +21,6 @@ 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; /** @@ -37,4 +36,7 @@ public @interface PluginValue { String value(); + + /** If false, standard configuration value substitution is not done on the referenced value. */ + boolean substitute() default true; } 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 index 65fcb767bcf..5b7d804d3c4 100644 --- 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.plugins; import java.lang.annotation.Annotation; @@ -23,7 +22,6 @@ 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; /** 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 index f4e421fe1ac..4ea2d134ee0 100644 --- 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 @@ -1,30 +1,30 @@ /* * 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.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.Constants; import org.apache.logging.log4j.util.LoaderUtil; /** - * @Since 2.9 + * @since 2.9 */ public class Base64Converter { @@ -40,14 +40,11 @@ public class Base64Converter { 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) { - + } catch (final ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException ex) { + // ignore } if (method == null) { try { @@ -68,12 +65,10 @@ public static byte[] parseBase64Binary(final String encoded) { } else { try { return (byte[]) method.invoke(decoder, encoded); - } catch (final IllegalAccessException ex) { - LOGGER.error("Error decoding string - " + ex.getMessage()); - } catch (final InvocationTargetException ex) { + } catch (final IllegalAccessException | InvocationTargetException ex) { LOGGER.error("Error decoding string - " + ex.getMessage()); } } - return new byte[0]; + return Constants.EMPTY_BYTE_ARRAY; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverter.java index 6fbc7aac8cf..60d97778be8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/DateTypeConverter.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.convert; @@ -35,11 +35,11 @@ public final class DateTypeConverter { static { final MethodHandles.Lookup lookup = MethodHandles.publicLookup(); - for (final Class dateClass : Arrays.asList(Date.class, java.sql.Date.class, Time.class, - Timestamp.class)) { + for (final Class dateClass : + Arrays.asList(Date.class, java.sql.Date.class, Time.class, Timestamp.class)) { try { - CONSTRUCTORS.put(dateClass, - lookup.findConstructor(dateClass, MethodType.methodType(void.class, long.class))); + CONSTRUCTORS.put( + dateClass, lookup.findConstructor(dateClass, MethodType.methodType(void.class, long.class))); } catch (final NoSuchMethodException | IllegalAccessException ignored) { // these classes all have this exact constructor } @@ -63,6 +63,5 @@ public static D fromMillis(final long millis, final Class ty } } - private DateTypeConverter() { - } + private DateTypeConverter() {} } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/EnumConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/EnumConverter.java index 15a162c708c..20a557cb2c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/EnumConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/EnumConverter.java @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.convert; 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..688300e1a34 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.convert; @@ -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 { @@ -28,8 +28,7 @@ public static byte[] parseHexBinary(final String s) { final int len = s.length(); final byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)); + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } 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 index e67e2134429..d75202872d1 100644 --- 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 @@ -1,20 +1,19 @@ /* * 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.logging.log4j.core.config.plugins.convert; /** 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 index 5088f1503e6..fa441304106 100644 --- 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 @@ -1,18 +1,18 @@ /* * 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.logging.log4j.core.config.plugins.convert; @@ -24,7 +24,6 @@ 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; @@ -84,10 +83,11 @@ public TypeConverter findCompatibleConverter(final Type type) { if (type instanceof Class) { final Class clazz = (Class) type; if (clazz.isEnum()) { - @SuppressWarnings({"unchecked","rawtypes"}) + @SuppressWarnings({"unchecked", "rawtypes"}) final EnumConverter converter = new EnumConverter(clazz.asSubclass(Enum.class)); - registry.putIfAbsent(type, converter); - return converter; + synchronized (INSTANCE_LOCK) { + return registerConverter(type, converter); + } } } // look for compatible converters @@ -96,8 +96,9 @@ public TypeConverter findCompatibleConverter(final Type type) { 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; + synchronized (INSTANCE_LOCK) { + return registerConverter(type, value); + } } } throw new UnknownFormatConversionException(type.toString()); @@ -116,18 +117,61 @@ private void loadKnownTypeConverters(final Collection> knownTypes) final Class clazz = knownType.getPluginClass(); if (TypeConverter.class.isAssignableFrom(clazz)) { @SuppressWarnings("rawtypes") - final Class pluginClass = clazz.asSubclass(TypeConverter.class); + 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); - } + registerConverter(conversionType, converter); + } + } + } + + /** + * Attempts to register the given converter and returns the effective + * converter associated with the given type. + *

    + * Registration will fail if there already exists a converter for the given + * type and neither the existing, nor the provided converter extends from {@link Comparable}. + */ + private TypeConverter registerConverter(final Type conversionType, final TypeConverter converter) { + final TypeConverter conflictingConverter = registry.get(conversionType); + if (conflictingConverter != null) { + final boolean overridable; + if (converter instanceof Comparable) { + @SuppressWarnings("unchecked") + final Comparable> comparableConverter = (Comparable>) converter; + overridable = comparableConverter.compareTo(conflictingConverter) < 0; + } else if (conflictingConverter instanceof Comparable) { + @SuppressWarnings("unchecked") + final Comparable> comparableConflictingConverter = + (Comparable>) conflictingConverter; + overridable = comparableConflictingConverter.compareTo(converter) > 0; + } else { + overridable = false; } + if (overridable) { + LOGGER.debug( + "Replacing TypeConverter [{}] for type [{}] with [{}] after comparison.", + conflictingConverter, + conversionType, + converter); + registry.put(conversionType, converter); + return converter; + } else { + LOGGER.warn( + "Ignoring TypeConverter [{}] for type [{}] that conflicts with [{}], since they are not comparable.", + converter, + conversionType, + conflictingConverter); + return conflictingConverter; + } + } else { + registry.put(conversionType, converter); + return converter; } } - private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class typeConverterClass) { + private static Type getTypeConverterSupportedType( + @SuppressWarnings("rawtypes") final Class typeConverterClass) { for (final Type type : typeConverterClass.getGenericInterfaces()) { if (type instanceof ParameterizedType) { final ParameterizedType pType = (ParameterizedType) type; @@ -154,5 +198,4 @@ private void registerPrimitiveTypes() { 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 index dc833f03061..6c81224c2d6 100644 --- 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 @@ -1,22 +1,24 @@ /* * 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.logging.log4j.core.config.plugins.convert; +import static org.apache.logging.log4j.util.Strings.toRootLowerCase; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; @@ -32,13 +34,12 @@ 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.Constants; import org.apache.logging.log4j.util.LoaderUtil; /** @@ -91,7 +92,7 @@ public Boolean convert(final String s) { /** * Converts a {@link String} into a {@code byte[]}. - * + * * The supported formats are: *